@obipascal/player 1.0.10 → 1.0.12
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 +197 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/player.d.ts +6 -0
- package/dist/src/react.d.ts +56 -1
- package/dist/src/types.d.ts +1 -1
- package/dist/wontum-player.cjs.js +18 -18
- package/dist/wontum-player.esm.js +418 -312
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -939,6 +939,77 @@ Each event includes Quality of Experience (QoE) metrics:
|
|
|
939
939
|
- `rebufferCount` - Number of rebuffer events
|
|
940
940
|
- `seekCount` - Number of seek operations
|
|
941
941
|
|
|
942
|
+
### React Hook (useAnalytics)
|
|
943
|
+
|
|
944
|
+
For React applications, use the `useAnalytics` hook for automatic lifecycle management:
|
|
945
|
+
|
|
946
|
+
```tsx
|
|
947
|
+
import { useAnalytics } from "@obipascal/player"
|
|
948
|
+
import { useEffect } from "react"
|
|
949
|
+
|
|
950
|
+
function VideoAnalyticsDashboard() {
|
|
951
|
+
const { trackEvent, getMetrics, connected, sessionId } = useAnalytics({
|
|
952
|
+
enabled: true,
|
|
953
|
+
endpoint: "https://api.example.com/analytics",
|
|
954
|
+
videoId: "video-123",
|
|
955
|
+
userId: "user-456",
|
|
956
|
+
webSocket: {
|
|
957
|
+
type: "socket.io",
|
|
958
|
+
url: "https://analytics.example.com",
|
|
959
|
+
auth: { token: "your-auth-token" },
|
|
960
|
+
eventName: "video_event",
|
|
961
|
+
},
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
// Track custom events
|
|
965
|
+
const handleShareClick = () => {
|
|
966
|
+
trackEvent("share_clicked", {
|
|
967
|
+
platform: "twitter",
|
|
968
|
+
videoTime: 125.5,
|
|
969
|
+
})
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const handleBookmark = () => {
|
|
973
|
+
trackEvent("bookmark_added", {
|
|
974
|
+
timestamp: Date.now(),
|
|
975
|
+
})
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Display metrics
|
|
979
|
+
useEffect(() => {
|
|
980
|
+
const interval = setInterval(() => {
|
|
981
|
+
const metrics = getMetrics()
|
|
982
|
+
console.log("Session Metrics:", metrics)
|
|
983
|
+
}, 5000)
|
|
984
|
+
|
|
985
|
+
return () => clearInterval(interval)
|
|
986
|
+
}, [getMetrics])
|
|
987
|
+
|
|
988
|
+
return (
|
|
989
|
+
<div>
|
|
990
|
+
<h3>Analytics Dashboard</h3>
|
|
991
|
+
<p>Session ID: {sessionId}</p>
|
|
992
|
+
<p>WebSocket Status: {connected ? "🟢 Connected" : "🔴 Disconnected"}</p>
|
|
993
|
+
|
|
994
|
+
<button onClick={handleShareClick}>Share Video</button>
|
|
995
|
+
<button onClick={handleBookmark}>Bookmark</button>
|
|
996
|
+
|
|
997
|
+
{/* The hook automatically tracks session_start and session_end */}
|
|
998
|
+
{/* It cleans up on component unmount */}
|
|
999
|
+
</div>
|
|
1000
|
+
)
|
|
1001
|
+
}
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
**Hook Features:**
|
|
1005
|
+
|
|
1006
|
+
- ✅ Automatic lifecycle management (initialization and cleanup)
|
|
1007
|
+
- ✅ WebSocket/Socket.IO connection status monitoring
|
|
1008
|
+
- ✅ Track custom events with `trackEvent()`
|
|
1009
|
+
- ✅ Access metrics with `getMetrics()`
|
|
1010
|
+
- ✅ Access all events with `getEvents()`
|
|
1011
|
+
- ✅ Session ID available immediately
|
|
1012
|
+
|
|
942
1013
|
## API Reference
|
|
943
1014
|
|
|
944
1015
|
### WontumPlayer
|
|
@@ -1099,6 +1170,69 @@ type PlayerEventType =
|
|
|
1099
1170
|
/>
|
|
1100
1171
|
```
|
|
1101
1172
|
|
|
1173
|
+
**Important: Changing Video Source**
|
|
1174
|
+
|
|
1175
|
+
The `WontumPlayerReact` component properly handles video source changes. When you update the `src` prop, the player will:
|
|
1176
|
+
|
|
1177
|
+
- ✅ Clean up the previous player instance completely
|
|
1178
|
+
- ✅ Remove all DOM elements (controls, progress bars)
|
|
1179
|
+
- ✅ Reinitialize with the new video source
|
|
1180
|
+
- ✅ Maintain control visibility and functionality
|
|
1181
|
+
|
|
1182
|
+
```tsx
|
|
1183
|
+
function VideoModal() {
|
|
1184
|
+
const [currentVideo, setCurrentVideo] = useState("video1.m3u8")
|
|
1185
|
+
|
|
1186
|
+
return (
|
|
1187
|
+
<WontumPlayerReact
|
|
1188
|
+
src={currentVideo} // ✅ Simply change the src - no need for React key tricks!
|
|
1189
|
+
width="100%"
|
|
1190
|
+
height="100%"
|
|
1191
|
+
controls
|
|
1192
|
+
stickyControls
|
|
1193
|
+
/>
|
|
1194
|
+
)
|
|
1195
|
+
}
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1198
|
+
**Advanced: Using updateSource() for Better Performance**
|
|
1199
|
+
|
|
1200
|
+
For even better performance when changing sources, you can use the `updateSource()` method via the `onReady` callback:
|
|
1201
|
+
|
|
1202
|
+
```tsx
|
|
1203
|
+
function VideoPlayer() {
|
|
1204
|
+
const [videos] = useState(["https://example.com/video1.m3u8", "https://example.com/video2.m3u8", "https://example.com/video3.m3u8"])
|
|
1205
|
+
const [currentIndex, setCurrentIndex] = useState(0)
|
|
1206
|
+
const playerRef = useRef<WontumPlayer | null>(null)
|
|
1207
|
+
|
|
1208
|
+
const handleReady = (player: WontumPlayer) => {
|
|
1209
|
+
playerRef.current = player
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const switchVideo = async (index: number) => {
|
|
1213
|
+
if (playerRef.current) {
|
|
1214
|
+
// Use updateSource for efficient source changes (no full reinitialization)
|
|
1215
|
+
await playerRef.current.updateSource(videos[index])
|
|
1216
|
+
setCurrentIndex(index)
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return (
|
|
1221
|
+
<div>
|
|
1222
|
+
<WontumPlayerReact src={videos[currentIndex]} width="100%" height="500px" onReady={handleReady} />
|
|
1223
|
+
|
|
1224
|
+
<div>
|
|
1225
|
+
{videos.map((_, index) => (
|
|
1226
|
+
<button key={index} onClick={() => switchVideo(index)} disabled={index === currentIndex}>
|
|
1227
|
+
Video {index + 1}
|
|
1228
|
+
</button>
|
|
1229
|
+
))}
|
|
1230
|
+
</div>
|
|
1231
|
+
</div>
|
|
1232
|
+
)
|
|
1233
|
+
}
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1102
1236
|
#### useWontumPlayer Hook
|
|
1103
1237
|
|
|
1104
1238
|
```tsx
|
|
@@ -1390,6 +1524,66 @@ pipButton.addEventListener("click", async () => {
|
|
|
1390
1524
|
|
|
1391
1525
|
The SDK includes a `WontumFileInfo` utility class to extract metadata from video files before uploading or processing them.
|
|
1392
1526
|
|
|
1527
|
+
#### React Hook (useVideoFileInfo)
|
|
1528
|
+
|
|
1529
|
+
For React applications, use the `useVideoFileInfo` hook for automatic state management:
|
|
1530
|
+
|
|
1531
|
+
```tsx
|
|
1532
|
+
import { useVideoFileInfo } from "@obipascal/player"
|
|
1533
|
+
import { useState } from "react"
|
|
1534
|
+
|
|
1535
|
+
function VideoUploader() {
|
|
1536
|
+
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
|
1537
|
+
const { info, loading, error, refetch } = useVideoFileInfo(selectedFile)
|
|
1538
|
+
|
|
1539
|
+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
1540
|
+
const file = event.target.files?.[0]
|
|
1541
|
+
setSelectedFile(file || null)
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
return (
|
|
1545
|
+
<div>
|
|
1546
|
+
<input type="file" accept="video/*" onChange={handleFileChange} />
|
|
1547
|
+
|
|
1548
|
+
{loading && <p>Analyzing video...</p>}
|
|
1549
|
+
|
|
1550
|
+
{error && (
|
|
1551
|
+
<div>
|
|
1552
|
+
<p style={{ color: "red" }}>Error: {error}</p>
|
|
1553
|
+
<button onClick={refetch}>Retry</button>
|
|
1554
|
+
</div>
|
|
1555
|
+
)}
|
|
1556
|
+
|
|
1557
|
+
{info && (
|
|
1558
|
+
<div>
|
|
1559
|
+
<h3>Video Information</h3>
|
|
1560
|
+
<ul>
|
|
1561
|
+
<li>
|
|
1562
|
+
Resolution: {info.width} × {info.height}
|
|
1563
|
+
</li>
|
|
1564
|
+
<li>Aspect Ratio: {info.aspectRatio}</li>
|
|
1565
|
+
<li>Quality: {info.quality}</li>
|
|
1566
|
+
<li>Duration: {info.durationFormatted}</li>
|
|
1567
|
+
<li>Size: {info.sizeFormatted}</li>
|
|
1568
|
+
<li>Bitrate: {info.bitrate} kbps</li>
|
|
1569
|
+
<li>Frame Rate: {info.frameRate} fps</li>
|
|
1570
|
+
<li>Audio: {info.hasAudio ? `${info.audioChannels} channels` : "No audio"}</li>
|
|
1571
|
+
</ul>
|
|
1572
|
+
|
|
1573
|
+
{/* Validation example */}
|
|
1574
|
+
{info.aspectRatio !== "16:9" && <p style={{ color: "orange" }}>⚠️ Video should be 16:9 aspect ratio</p>}
|
|
1575
|
+
{info.height < 720 && <p style={{ color: "red" }}>❌ Minimum resolution is 720p</p>}
|
|
1576
|
+
{!info.hasAudio && <p style={{ color: "red" }}>❌ Video must have audio</p>}
|
|
1577
|
+
{info.audioChannels !== 2 && <p style={{ color: "orange" }}>⚠️ Audio should be stereo (2 channels)</p>}
|
|
1578
|
+
</div>
|
|
1579
|
+
)}
|
|
1580
|
+
</div>
|
|
1581
|
+
)
|
|
1582
|
+
}
|
|
1583
|
+
```
|
|
1584
|
+
|
|
1585
|
+
#### Vanilla JavaScript
|
|
1586
|
+
|
|
1393
1587
|
```typescript
|
|
1394
1588
|
import { WontumFileInfo } from "@obipascal/player"
|
|
1395
1589
|
|
|
@@ -1553,10 +1747,11 @@ For detailed API documentation including all methods, events, types, and configu
|
|
|
1553
1747
|
- **Playback Rate:** `setPlaybackRate(rate)`
|
|
1554
1748
|
- **Fullscreen:** `enterFullscreen()`, `exitFullscreen()`
|
|
1555
1749
|
- **Picture-in-Picture:** `enterPictureInPicture()`, `exitPictureInPicture()`, `togglePictureInPicture()`
|
|
1750
|
+
- **Source Management:** `updateSource(src)` - _Efficiently change video source without full reinitialization_
|
|
1556
1751
|
- **State:** `getState()`, `getCurrentTime()`, `getDuration()`
|
|
1557
1752
|
- **Lifecycle:** `destroy()`
|
|
1558
1753
|
|
|
1559
|
-
**Events (
|
|
1754
|
+
**Events (26 total):**
|
|
1560
1755
|
|
|
1561
1756
|
- **Playback:** `play`, `pause`, `ended`, `timeupdate`, `durationchange`
|
|
1562
1757
|
- **Loading:** `loadstart`, `loadedmetadata`, `loadeddata`, `canplay`, `canplaythrough`
|
|
@@ -1564,6 +1759,7 @@ For detailed API documentation including all methods, events, types, and configu
|
|
|
1564
1759
|
- **Seeking:** `seeking`, `seeked`
|
|
1565
1760
|
- **Volume:** `volumechange`
|
|
1566
1761
|
- **Quality:** `qualitychange`, `renditionchange`
|
|
1762
|
+
- **Source:** `sourcechange` - _Fires when video source is changed via updateSource()_
|
|
1567
1763
|
- **Errors:** `error`
|
|
1568
1764
|
- **Playback Rate:** `ratechange`
|
|
1569
1765
|
- **Fullscreen:** `fullscreenchange`
|
package/dist/src/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { Analytics } from './analytics';
|
|
|
3
3
|
export { S3Handler } from './s3-handler';
|
|
4
4
|
export { UIController } from './ui-controller';
|
|
5
5
|
export { WontumFileInfo } from './file-info';
|
|
6
|
-
export { WontumPlayerReact, useWontumPlayer, WontumPlayerProvider, useWontumPlayerContext } from './react';
|
|
6
|
+
export { WontumPlayerReact, useWontumPlayer, WontumPlayerProvider, useWontumPlayerContext, useVideoFileInfo, useAnalytics } from './react';
|
|
7
7
|
export type { WontumPlayerConfig, PlayerTheme, S3Config, AnalyticsConfig, WebSocketAnalyticsHandler, SocketIOAnalyticsHandler, PlayerState, PlayerEvent, PlayerEventType, AnalyticsEvent, QualityLevel, } from './types';
|
|
8
8
|
export type { VideoFileInfo } from './file-info';
|
|
9
|
-
export type { WontumPlayerReactProps } from './react';
|
|
9
|
+
export type { WontumPlayerReactProps, UseVideoFileInfoResult, UseAnalyticsResult } from './react';
|
package/dist/src/player.d.ts
CHANGED
|
@@ -64,5 +64,11 @@ export declare class WontumPlayer {
|
|
|
64
64
|
on(eventType: PlayerEventType, callback: (event: PlayerEvent) => void): void;
|
|
65
65
|
off(eventType: PlayerEventType, callback: (event: PlayerEvent) => void): void;
|
|
66
66
|
private emit;
|
|
67
|
+
/**
|
|
68
|
+
* Update video source without recreating the entire player
|
|
69
|
+
* This is more efficient than destroying and recreating the player
|
|
70
|
+
* @param src - New video source URL
|
|
71
|
+
*/
|
|
72
|
+
updateSource(src: string): Promise<void>;
|
|
67
73
|
destroy(): void;
|
|
68
74
|
}
|
package/dist/src/react.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WontumPlayer } from './player';
|
|
2
|
-
import { WontumPlayerConfig, PlayerState } from './types';
|
|
2
|
+
import { WontumPlayerConfig, PlayerState, AnalyticsConfig, AnalyticsEvent } from './types';
|
|
3
|
+
import { VideoFileInfo } from './file-info';
|
|
3
4
|
import * as React from "react";
|
|
4
5
|
export interface WontumPlayerReactProps extends Omit<WontumPlayerConfig, "container"> {
|
|
5
6
|
/** Callback when player is ready */
|
|
@@ -46,4 +47,58 @@ export declare const WontumPlayerProvider: React.FC<{
|
|
|
46
47
|
children: React.ReactNode;
|
|
47
48
|
}>;
|
|
48
49
|
export declare const useWontumPlayerContext: () => WontumPlayerContextValue;
|
|
50
|
+
/**
|
|
51
|
+
* Hook for extracting video file information
|
|
52
|
+
* @param file - The video file to analyze (File object or null)
|
|
53
|
+
* @returns Object containing loading state, error, and video info
|
|
54
|
+
*/
|
|
55
|
+
export interface UseVideoFileInfoResult {
|
|
56
|
+
/** Video file information (null if not loaded or error occurred) */
|
|
57
|
+
info: VideoFileInfo | null;
|
|
58
|
+
/** Whether extraction is in progress */
|
|
59
|
+
loading: boolean;
|
|
60
|
+
/** Error message if extraction failed */
|
|
61
|
+
error: string | null;
|
|
62
|
+
/** Re-extract file info (useful for retry after error) */
|
|
63
|
+
refetch: () => Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
export declare const useVideoFileInfo: (file: File | null | undefined) => UseVideoFileInfoResult;
|
|
66
|
+
/**
|
|
67
|
+
* Result type for useAnalytics hook
|
|
68
|
+
*/
|
|
69
|
+
export interface UseAnalyticsResult {
|
|
70
|
+
/** Track a custom event */
|
|
71
|
+
trackEvent: (eventType: string, data?: Record<string, any>) => void;
|
|
72
|
+
/** Get all tracked events */
|
|
73
|
+
getEvents: () => AnalyticsEvent[];
|
|
74
|
+
/** Get analytics metrics (session duration, buffer time, etc.) */
|
|
75
|
+
getMetrics: () => Record<string, any>;
|
|
76
|
+
/** WebSocket connection status (for websocket/socket.io analytics) */
|
|
77
|
+
connected: boolean;
|
|
78
|
+
/** Session ID for this analytics instance */
|
|
79
|
+
sessionId: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* React hook for analytics tracking
|
|
83
|
+
* Automatically handles lifecycle management and cleanup
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```tsx
|
|
87
|
+
* const { trackEvent, getMetrics, connected } = useAnalytics({
|
|
88
|
+
* enabled: true,
|
|
89
|
+
* endpoint: "https://api.example.com/analytics",
|
|
90
|
+
* videoId: "video-123",
|
|
91
|
+
* userId: "user-456",
|
|
92
|
+
* webSocket: {
|
|
93
|
+
* type: "socket.io",
|
|
94
|
+
* url: "https://analytics.example.com",
|
|
95
|
+
* auth: { token: "your-token" }
|
|
96
|
+
* }
|
|
97
|
+
* })
|
|
98
|
+
*
|
|
99
|
+
* // Track custom events
|
|
100
|
+
* trackEvent("button_clicked", { buttonName: "share" })
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export declare const useAnalytics: (config?: AnalyticsConfig) => UseAnalyticsResult;
|
|
49
104
|
export {};
|
package/dist/src/types.d.ts
CHANGED
|
@@ -130,7 +130,7 @@ export interface PlayerState {
|
|
|
130
130
|
/**
|
|
131
131
|
* Player events (compatible with Mux Player and HTML5 MediaElement events)
|
|
132
132
|
*/
|
|
133
|
-
export type PlayerEventType = "play" | "pause" | "playing" | "ended" | "timeupdate" | "volumechange" | "ratechange" | "seeked" | "seeking" | "waiting" | "loadstart" | "loadeddata" | "loadedmetadata" | "canplay" | "canplaythrough" | "durationchange" | "progress" | "error" | "abort" | "emptied" | "stalled" | "suspend" | "qualitychange" | "fullscreenchange" | "pictureinpictureenter" | "pictureinpictureexit" | "resize";
|
|
133
|
+
export type PlayerEventType = "play" | "pause" | "playing" | "ended" | "timeupdate" | "volumechange" | "ratechange" | "seeked" | "seeking" | "waiting" | "loadstart" | "loadeddata" | "loadedmetadata" | "canplay" | "canplaythrough" | "durationchange" | "progress" | "error" | "abort" | "emptied" | "stalled" | "suspend" | "qualitychange" | "fullscreenchange" | "pictureinpictureenter" | "pictureinpictureexit" | "resize" | "sourcechange";
|
|
134
134
|
export interface PlayerEvent {
|
|
135
135
|
type: PlayerEventType;
|
|
136
136
|
data?: any;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var j=Object.create;var q=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var X=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var K=(l,t,e)=>t in l?q(l,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):l[t]=e;var J=(l,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Y(t))!G.call(l,i)&&i!==e&&q(l,i,{get:()=>t[i],enumerable:!(n=Q(t,i))||n.enumerable});return l};var Z=(l,t,e)=>(e=l!=null?j(X(l)):{},J(t||!l||!l.__esModule?q(e,"default",{value:l,enumerable:!0}):e,l));var a=(l,t,e)=>K(l,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("hls.js"),O=require("react/jsx-runtime"),d=require("react");function tt(l){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(l){for(const e in l)if(e!=="default"){const n=Object.getOwnPropertyDescriptor(l,e);Object.defineProperty(t,e,n.get?n:{enumerable:!0,get:()=>l[e]})}}return t.default=l,Object.freeze(t)}const z=tt(d);class F{constructor(t){a(this,"config");a(this,"sessionId");a(this,"events",[]);a(this,"sessionStartTime");a(this,"playbackStartTime",null);a(this,"totalPlayTime",0);a(this,"totalBufferTime",0);a(this,"bufferStartTime",null);a(this,"rebufferCount",0);a(this,"seekCount",0);a(this,"webSocket",null);a(this,"socketIO",null);a(this,"wsReconnectTimeout",null);a(this,"isDestroyed",!1);var e,n;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 i=this.config.webSocket;"type"in i?i.type==="socket.io"?this.initializeSocketIO():this.initializeWebSocket():this.initializeWebSocket()}(n=this.config)!=null&&n.enabled&&this.trackEvent("session_start",this.getSessionData())}trackEvent(t,e={}){var i;if(!((i=this.config)!=null&&i.enabled))return;const n={eventType:t,timestamp:Date.now(),sessionId:this.sessionId,videoId:this.config.videoId,userId:this.config.userId,data:{...e,...this.getQoEMetrics()}};this.events.push(n),this.updateMetrics(t,e),this.webSocket&&this.webSocket.readyState===WebSocket.OPEN&&this.sendToWebSocket(n),this.socketIO&&this.socketIO.connected&&this.sendToSocketIO(n),this.config.endpoint&&this.sendEvent(n),process.env.NODE_ENV==="development"&&console.log("[Analytics]",t,n.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(n){console.error("Failed to send analytics event:",n)}}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 i=(await import("socket.io-client")).default;this.socketIO=i(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",n=>{console.error("[Analytics Socket.IO] Connection error:",n),t.onError&&t.onError(n)}),this.socketIO.on("disconnect",n=>{process.env.NODE_ENV==="development"&&console.log("[Analytics Socket.IO] Disconnected:",n),t.onDisconnect&&t.onDisconnect(n)}),this.socketIO.on("error",n=>{console.error("[Analytics Socket.IO] Error:",n),t.onError&&t.onError(n)})}catch(n){console.error("[Analytics Socket.IO] Failed to initialize:",n)}}sendToSocketIO(t){var e;if(!(!this.socketIO||!this.socketIO.connected))try{const n=(e=this.config)==null?void 0:e.webSocket,i=n!=null&&n.transform?n.transform(t):t,o=(n==null?void 0:n.eventName)||"analytics";this.socketIO.emit(o,i),process.env.NODE_ENV==="development"&&console.log(`[Analytics Socket.IO] Emitted (${o}):`,t.eventType)}catch(n){console.error("[Analytics Socket.IO] Failed to emit event:",n)}}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=n=>{process.env.NODE_ENV==="development"&&console.log("[Analytics WebSocket] Connected"),t.onOpen&&t.onOpen(n)},this.webSocket.onerror=n=>{console.error("[Analytics WebSocket] Error:",n),t.onError&&t.onError(n)},this.webSocket.onclose=n=>{if(process.env.NODE_ENV==="development"&&console.log("[Analytics WebSocket] Disconnected"),t.onClose&&t.onClose(n),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(n){console.error("[Analytics WebSocket] Failed to initialize:",n)}}sendToWebSocket(t){var e;if(!(!this.webSocket||this.webSocket.readyState!==WebSocket.OPEN))try{const n=(e=this.config)==null?void 0:e.webSocket,i=n!=null&&n.transform?n.transform(t):t;this.webSocket.send(JSON.stringify(i)),process.env.NODE_ENV==="development"&&console.log("[Analytics WebSocket] Sent:",t.eventType)}catch(n){console.error("[Analytics WebSocket] Failed to send event:",n)}}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 D{constructor(t,e){a(this,"container");a(this,"player");a(this,"controlsContainer");a(this,"progressContainer");a(this,"progressBar");a(this,"playButton");a(this,"skipBackwardButton");a(this,"skipForwardButton");a(this,"volumeButton");a(this,"volumeContainer");a(this,"fullscreenButton");a(this,"pipButton");a(this,"settingsButton");a(this,"volumeSlider");a(this,"progressInput");a(this,"hideControlsTimeout",null);a(this,"stickyControls",!1);a(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||{},n=e.primaryColor||"#3b82f6",i=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)",r=e.buttonHoverBg||"rgba(255, 255, 255, 0.1)",c=e.progressHeight||"6px",u=e.borderRadius||"4px",h=document.createElement("style");h.id=t,h.textContent=`
|
|
2
2
|
.wontum-player-container {
|
|
3
3
|
position: relative;
|
|
4
4
|
background: #000;
|
|
5
5
|
font-family: ${o};
|
|
6
6
|
overflow: hidden;
|
|
7
|
-
--primary-color: ${
|
|
8
|
-
--accent-color: ${
|
|
7
|
+
--primary-color: ${n};
|
|
8
|
+
--accent-color: ${i};
|
|
9
9
|
--controls-bg: ${s};
|
|
10
|
-
--button-hover: ${
|
|
10
|
+
--button-hover: ${r};
|
|
11
11
|
--progress-height: ${c};
|
|
12
12
|
--border-radius: ${u};
|
|
13
13
|
}
|
|
@@ -486,7 +486,7 @@
|
|
|
486
486
|
transform: translateY(0) !important;
|
|
487
487
|
pointer-events: all !important;
|
|
488
488
|
}
|
|
489
|
-
`,document.head.appendChild(
|
|
489
|
+
`,document.head.appendChild(h),this.container.classList.add("wontum-player-container")}createProgressBar(){const t=document.createElement("div");t.className="wontum-progress-container",t.innerHTML=`
|
|
490
490
|
<div class="wontum-progress-track"></div>
|
|
491
491
|
<div class="wontum-progress-filled"></div>
|
|
492
492
|
<input type="range" class="wontum-progress-input" min="0" max="100" value="0" step="0.1">
|
|
@@ -562,28 +562,28 @@
|
|
|
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",
|
|
566
|
-
<div class="wontum-subtitle-option ${
|
|
567
|
-
${e.map((
|
|
568
|
-
<div class="wontum-subtitle-option ${o===
|
|
569
|
-
${
|
|
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",n=>{const i=n.target,o=parseFloat(i.value),s=this.player.getState(),r=o/100*s.duration;this.player.seek(r)}),this.volumeSlider.addEventListener("input",n=>{const i=n.target,o=parseFloat(i.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(n){console.error("PiP error:",n)}}),this.settingsButton.addEventListener("click",()=>{const n=this.controlsContainer.querySelector(".wontum-settings-panel");n.classList.toggle("active"),n.classList.contains("active")&&(this.updateSettingsMenu(),this.updateQualityMenu(),this.updateSpeedMenu(),this.updateSubtitleMenu())});const t=this.controlsContainer.querySelectorAll(".wontum-tab");t.forEach(n=>{n.addEventListener("click",i=>{const o=i.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,n=this.player.getState();if(n.duration>0){const o=e/n.duration*100;this.progressBar.style.width=`${o}%`,this.progressInput.value=o.toString()}const i=this.controlsContainer.querySelector(".wontum-current-time");i.textContent=this.formatTime(e)}),this.player.on("loadedmetadata",t=>{const{duration:e}=t.data,n=this.controlsContainer.querySelector(".wontum-duration");n.textContent=this.formatTime(e),t.data.qualities&&this.updateQualityMenu(t.data.qualities)}),this.player.on("volumechange",t=>{const{volume:e,muted:n}=t.data;this.volumeSlider.value=(e*100).toString(),this.volumeButton.innerHTML=n?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 n=e.findIndex(i=>i.mode==="showing");t.innerHTML=`
|
|
566
|
+
<div class="wontum-subtitle-option ${n===-1?"active":""}" data-track="-1">Off</div>
|
|
567
|
+
${e.map((i,o)=>`
|
|
568
|
+
<div class="wontum-subtitle-option ${o===n?"active":""}" data-track="${o}">
|
|
569
|
+
${i.label||i.language||`Track ${o+1}`}
|
|
570
570
|
</div>
|
|
571
571
|
`).join("")}
|
|
572
|
-
`,t.querySelectorAll(".wontum-subtitle-option").forEach(
|
|
573
|
-
<div class="wontum-speed-option ${
|
|
572
|
+
`,t.querySelectorAll(".wontum-subtitle-option").forEach(i=>{i.addEventListener("click",o=>{const s=o.target,r=parseInt(s.dataset.track||"-1");r===-1?this.player.disableSubtitles():this.player.enableSubtitles(r),t.querySelectorAll(".wontum-subtitle-option").forEach(c=>c.classList.remove("active")),s.classList.add("active")})})}updateSpeedMenu(){const t=this.controlsContainer.querySelector(".wontum-speed-menu"),n=this.player.getState().playbackRate||1,i=[.25,.5,.75,1,1.25,1.5,1.75,2];t.innerHTML=i.map(o=>`
|
|
573
|
+
<div class="wontum-speed-option ${n===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",s=>{const
|
|
576
|
+
`).join(""),t.querySelectorAll(".wontum-speed-option").forEach(o=>{o.addEventListener("click",s=>{const r=s.target,c=parseFloat(r.dataset.speed||"1");this.player.setPlaybackRate(c),t.querySelectorAll(".wontum-speed-option").forEach(u=>u.classList.remove("active")),r.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>
|
|
580
580
|
</div>
|
|
581
|
-
`;const e=t.querySelector('[data-setting="sticky-controls"]');e.addEventListener("click",()=>{this.stickyControls=!this.stickyControls,e.querySelector(".wontum-toggle-switch").classList.toggle("active"),this.stickyControls?(this.controlsContainer.classList.add("sticky"),this.progressContainer.classList.add("sticky")):(this.controlsContainer.classList.remove("sticky"),this.progressContainer.classList.remove("sticky"))})}updateQualityMenu(t){const e=this.controlsContainer.querySelector(".wontum-quality-menu"),
|
|
581
|
+
`;const e=t.querySelector('[data-setting="sticky-controls"]');e.addEventListener("click",()=>{this.stickyControls=!this.stickyControls,e.querySelector(".wontum-toggle-switch").classList.toggle("active"),this.stickyControls?(this.controlsContainer.classList.add("sticky"),this.progressContainer.classList.add("sticky")):(this.controlsContainer.classList.remove("sticky"),this.progressContainer.classList.remove("sticky"))})}updateQualityMenu(t){const e=this.controlsContainer.querySelector(".wontum-quality-menu"),n=t||this.player.getQualities();if(!n||n.length===0){e.innerHTML='<div class="wontum-quality-option">No qualities available</div>';return}e.innerHTML=`
|
|
582
582
|
<div class="wontum-quality-option active" data-quality="-1">Auto</div>
|
|
583
|
-
${
|
|
584
|
-
<div class="wontum-quality-option" data-quality="${o}">${
|
|
583
|
+
${n.map((i,o)=>`
|
|
584
|
+
<div class="wontum-quality-option" data-quality="${o}">${i.name}</div>
|
|
585
585
|
`).join("")}
|
|
586
|
-
`,e.querySelectorAll(".wontum-quality-option").forEach(
|
|
586
|
+
`,e.querySelectorAll(".wontum-quality-option").forEach(i=>{i.addEventListener("click",o=>{const s=o.target,r=parseInt(s.dataset.quality||"-1");this.player.setQuality(r),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),n=Math.floor(t%60);return`${e}:${n.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 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;
|
|
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&&this.controlsContainer.remove(),this.progressContainer&&this.progressContainer.remove()}}class ${constructor(t){a(this,"config");a(this,"urlCache",new Map);a(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 n=new URL(t);return this.config.cloudFrontDomains.some(i=>n.hostname.includes(i))}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 r=await this.config.signUrl(t);return this.signedUrls.add(t),r}catch(r){const c=(r==null?void 0:r.name)==="AbortError"||((s=r==null?void 0:r.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:",r),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: ${(r==null?void 0:r.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 n=t.match(/([^.]+)\.s3\.amazonaws\.com\/(.+)/);return n?n[2]:t}async getPresignedUrl(t){var i;const e=this.extractS3Key(t),n=this.urlCache.get(e);if(n&&n.expiresAt>Date.now())return n.url;if((i=this.config)!=null&&i.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,n="us-east-1"){return`https://${t}.s3.${n}.amazonaws.com/${e}`}static parseS3Uri(t){if(!t.startsWith("s3://"))return null;const e=t.replace("s3://","").split("/"),n=e[0],i=e.slice(1).join("/");return{bucket:n,key:i}}clearCache(){this.urlCache.clear(),this.signedUrls.clear()}}class B{constructor(t){a(this,"container");a(this,"videoElement");a(this,"hls",null);a(this,"config");a(this,"eventListeners",new Map);a(this,"analytics");a(this,"s3Handler");a(this,"uiController");a(this,"qualities",[]);a(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 F(t.analytics),this.s3Handler=new $(t.s3Config),this.videoElement=this.createVideoElement(),this.container.appendChild(this.videoElement),this.uiController=new D(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 n=document.createElement("track");n.kind="subtitles",n.label=e.label,n.src=e.src,n.srclang=e.srclang,e.default&&(n.default=!0),this.videoElement.appendChild(n)})}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 n=await this.s3Handler.processUrl(t);if(y.isSupported()){const i=((e=this.config.s3Config)==null?void 0:e.withCredentials)??!1,o={...this.config.hlsConfig,xhrSetup:(s,r)=>{var c;i&&(s.withCredentials=!0),(c=this.config.hlsConfig)!=null&&c.xhrSetup&&this.config.hlsConfig.xhrSetup(s,r)}};this.hls=new y(o),this.hls.loadSource(n),this.hls.attachMedia(this.videoElement),this.hls.on(y.Events.MANIFEST_PARSED,(s,r)=>{const c=this.extractQualities(r.levels);this.qualities=c}),this.hls.on(y.Events.LEVEL_SWITCHED,(s,r)=>{var u;const c=(u=this.hls)==null?void 0:u.levels[r.level];c&&(this.state.quality=`${c.height}p`,this.emit("qualitychange",{quality:this.state.quality}))}),this.hls.on(y.Events.ERROR,(s,r)=>{r.fatal&&this.handleHlsError(r)})}else if(this.videoElement.canPlayType("application/vnd.apple.mpegurl"))this.videoElement.src=n;else throw new Error("HLS is not supported in this browser")}catch(n){console.error("Failed to load video source:",n),this.emit("error",{error:n})}}extractQualities(t){return t.map(e=>({height:e.height,width:e.width,bitrate:e.bitrate,name:`${e.height}p`}))}handleHlsError(t){var e,n;switch(t.type){case y.ErrorTypes.NETWORK_ERROR:console.error("Network error occurred"),(e=this.hls)==null||e.startLoad();break;case y.ErrorTypes.MEDIA_ERROR:console.error("Media error occurred"),(n=this.hls)==null||n.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 n=0;n<e.length;n++)e[n].mode=n===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(n=>n.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 n;(n=this.eventListeners.get(t))==null||n.delete(e)}emit(t,e){var i;const n={type:t,data:e,timestamp:Date.now()};(i=this.eventListeners.get(t))==null||i.forEach(o=>{o(n)})}async updateSource(t){this.pause(),this.state.currentTime=0,this.state.ended=!1,this.state.buffering=!1,this.hls&&(this.hls.destroy(),this.hls=null),this.config.src=t,await this.loadSource(t),this.emit("sourcechange",{src:t})}destroy(){this.hls&&(this.hls.destroy(),this.hls=null),this.uiController.destroy(),this.videoElement.remove(),this.eventListeners.clear(),this.analytics.destroy()}}class U{constructor(t){a(this,"file");a(this,"videoElement",null);a(this,"audioContext",null);a(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"],n=t.name.toLowerCase();return e.some(i=>n.endsWith(i))}async extract(){return new Promise((t,e)=>{try{this.videoElement=document.createElement("video"),this.videoElement.preload="metadata",this.videoElement.muted=!0;const n=URL.createObjectURL(this.file);this.videoElement.onloadedmetadata=async()=>{try{if(!this.videoElement){e(new Error("Video element not initialized"));return}const i=this.videoElement.videoWidth,o=this.videoElement.videoHeight,s=this.videoElement.duration,r=this.calculateAspectRatio(i,o),c=this.file.size,u=this.formatBytes(c),h=this.formatDuration(s),p=this.getFileExtension(this.file.name),m=s>0?Math.round(c*8/s/1e3):void 0,b=await this.detectFrameRate(),w=await this.detectAudioInfo(n);this.info={width:i,height:o,aspectRatio:r,size:c,sizeInBytes:c,sizeFormatted:u,duration:s,durationInSeconds:s,durationFormatted:h,mimeType:this.file.type||"video/unknown",fileName:this.file.name,fileExtension:p,bitrate:m,frameRate:b,audioChannels:w.channels,hasAudio:w.hasAudio},URL.revokeObjectURL(n),this.videoElement.remove(),t(this.info)}catch(i){URL.revokeObjectURL(n),e(i)}},this.videoElement.onerror=()=>{URL.revokeObjectURL(n),e(new Error(`Failed to load video file: ${this.file.name}`))},this.videoElement.src=n}catch(n){e(n)}})}calculateAspectRatio(t,e){const n=this.getGCD(t,e),i=t/n,o=e/n,s=i/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":`${i}:${o}`}async detectFrameRate(){if(this.videoElement)try{return"requestVideoFrameCallback"in this.videoElement?new Promise(t=>{let e=0,n=0;const i=10,o=(s,r)=>{if(!this.videoElement){t(void 0);return}if(e++,e===1)n=s,this.videoElement.requestVideoFrameCallback(o);else if(e<i)this.videoElement.requestVideoFrameCallback(o);else{const c=(s-n)/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 i=window.AudioContext||window.webkitAudioContext;if(!i)return{hasAudio:!0};this.audioContext=new i;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,n=["Bytes","KB","MB","GB","TB"],i=Math.floor(Math.log(t)/Math.log(e));return`${parseFloat((t/Math.pow(e,i)).toFixed(2))} ${n[i]}`}formatDuration(t){if(!isFinite(t)||t<0)return"00:00";const e=Math.floor(t/3600),n=Math.floor(t%3600/60),i=Math.floor(t%60);return e>0?`${e.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}`:`${n.toString().padStart(2,"0")}:${i.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 et=l=>{const{src:t,autoplay:e,muted:n,controls:i=!0,poster:o,preload:s,theme:r,s3Config:c,analytics:u,hlsConfig:h,subtitles:p,stickyControls:m,onReady:b,onPlay:w,onPause:x,onEnded:C,onTimeUpdate:L,onVolumeChange:I,onError:T,onLoadedMetadata:A,onQualityChange:R,style:W,className:H,width:P="100%",height:M="500px"}=l,v=d.useRef(null),E=d.useRef(null);return d.useEffect(()=>{if(!v.current)return;if(v.current){const f=v.current.querySelectorAll(".wontum-player-video"),S=v.current.querySelectorAll(".wontum-controls"),_=v.current.querySelectorAll(".wontum-progress-container");f.forEach(k=>k.remove()),S.forEach(k=>k.remove()),_.forEach(k=>k.remove())}const N={src:t,container:v.current,autoplay:e,muted:n,controls:i,poster:o,preload:s,theme:r,s3Config:c,analytics:u,hlsConfig:h,subtitles:p,stickyControls:m},g=new B(N);return E.current=g,w&&g.on("play",w),x&&g.on("pause",x),C&&g.on("ended",C),T&&g.on("error",f=>{var S;return T((S=f.data)==null?void 0:S.error)}),A&&g.on("loadedmetadata",A),R&&g.on("qualitychange",f=>R(f.data.level)),L&&g.on("timeupdate",f=>L(f.data.currentTime)),I&&g.on("volumechange",f=>I(f.data.volume,f.data.muted)),b&&b(g),()=>{E.current&&(E.current.destroy(),E.current=null)}},[t,e,n,i,o,s,r,c,u,h,p,m,w,x,C,T,A,R,L,I,b]),O.jsx("div",{ref:v,className:H,style:{width:typeof P=="number"?`${P}px`:P,height:typeof M=="number"?`${M}px`:M,...W}})},nt=l=>{const[t,e]=d.useState(null),[n,i]=d.useState(null),o=d.useRef(null);return d.useEffect(()=>{if(!o.current)return;const s=new B({...l,container:o.current});e(s);const r=()=>{i(s.getState())};return s.on("play",r),s.on("pause",r),s.on("timeupdate",r),s.on("volumechange",r),s.on("loadedmetadata",r),()=>{s.destroy()}},[l.src]),{containerRef:o,player:t,state:n}},V=z.createContext({player:null,state:null}),it=l=>{const{player:t,children:e}=l,[n,i]=d.useState(t.getState());return d.useEffect(()=>{const o=()=>{i(t.getState())};return t.on("play",o),t.on("pause",o),t.on("timeupdate",o),t.on("volumechange",o),t.on("loadedmetadata",o),()=>{}},[t]),O.jsx(V.Provider,{value:{player:t,state:n},children:e})},ot=()=>{const l=z.useContext(V);if(!l.player)throw new Error("useWontumPlayerContext must be used within WontumPlayerProvider");return l},st=l=>{const[t,e]=d.useState(null),[n,i]=d.useState(!1),[o,s]=d.useState(null),r=d.useRef(null),c=d.useCallback(async()=>{if(!l){e(null),s(null),i(!1);return}i(!0),s(null);try{r.current&&(r.current.destroy(),r.current=null);const u=new U(l);r.current=u;const h=await u.extract();e(h),s(null)}catch(u){const h=u instanceof Error?u.message:"Failed to extract video information";s(h),e(null)}finally{i(!1)}},[l]);return d.useEffect(()=>(c(),()=>{r.current&&(r.current.destroy(),r.current=null)}),[c]),{info:t,loading:n,error:o,refetch:c}},rt=l=>{const t=d.useRef(null),[e,n]=d.useState(!1),[i,o]=d.useState("");d.useEffect(()=>{const u=new F(l);if(t.current=u,o(u.getMetrics().sessionId),l!=null&&l.webSocket){const h=setInterval(()=>{if(!t.current){n(!1);return}const p=l.webSocket;if(p&&"type"in p&&p.type==="websocket"){const m=t.current.webSocket;n((m==null?void 0:m.readyState)===WebSocket.OPEN)}else if(p&&"type"in p&&p.type==="socket.io"){const m=t.current.socketIO;n((m==null?void 0:m.connected)??!1)}else if(p){const m=t.current.webSocket;n((m==null?void 0:m.readyState)===WebSocket.OPEN)}},1e3);return()=>{clearInterval(h),t.current&&(t.current.destroy(),t.current=null)}}return()=>{t.current&&(t.current.destroy(),t.current=null)}},[l]);const s=d.useCallback((u,h)=>{var p;(p=t.current)==null||p.trackEvent(u,h)},[]),r=d.useCallback(()=>{var u;return((u=t.current)==null?void 0:u.getEvents())??[]},[]),c=d.useCallback(()=>{var u;return((u=t.current)==null?void 0:u.getMetrics())??{}},[]);return{trackEvent:s,getEvents:r,getMetrics:c,connected:e,sessionId:i}};exports.Analytics=F;exports.S3Handler=$;exports.UIController=D;exports.WontumFileInfo=U;exports.WontumPlayer=B;exports.WontumPlayerProvider=it;exports.WontumPlayerReact=et;exports.useAnalytics=rt;exports.useVideoFileInfo=st;exports.useWontumPlayer=nt;exports.useWontumPlayerContext=ot;
|