@optifye/dashboard-core 6.9.11 → 6.9.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/dist/index.js CHANGED
@@ -2925,8 +2925,8 @@ var AuthService = class {
2925
2925
  "Authorization": `Bearer ${accessToken}`,
2926
2926
  "Content-Type": "application/json"
2927
2927
  },
2928
- timeout: 3e5,
2929
- // 5 minutes
2928
+ timeout: 1e4,
2929
+ // 10 seconds
2930
2930
  retries: 1,
2931
2931
  silentErrors: false
2932
2932
  // We want to know about auth errors
@@ -2963,8 +2963,8 @@ var AuthService = class {
2963
2963
  "Authorization": `Bearer ${accessToken}`,
2964
2964
  "Content-Type": "application/json"
2965
2965
  },
2966
- timeout: 3e5,
2967
- // 5 minutes
2966
+ timeout: 1e4,
2967
+ // 10 seconds
2968
2968
  retries: 2,
2969
2969
  // More retries for validation
2970
2970
  silentErrors: true,
@@ -2996,8 +2996,8 @@ var AuthService = class {
2996
2996
  "Authorization": `Bearer ${accessToken}`,
2997
2997
  "Content-Type": "application/json"
2998
2998
  },
2999
- timeout: 3e5,
3000
- // 5 minutes
2999
+ timeout: 1e4,
3000
+ // 10 seconds
3001
3001
  retries: 1,
3002
3002
  silentErrors: false
3003
3003
  }
@@ -10912,7 +10912,7 @@ function useDateFormatter() {
10912
10912
  },
10913
10913
  [defaultTimezone, defaultLocale, dateFormatOptions]
10914
10914
  );
10915
- const formatTime3 = React23.useCallback(
10915
+ const formatTime4 = React23.useCallback(
10916
10916
  (date, formatString) => {
10917
10917
  const dateObj = typeof date === "string" ? dateFns.parseISO(date) : date;
10918
10918
  if (!dateFns.isValid(dateObj)) return "Invalid Time";
@@ -10943,7 +10943,7 @@ function useDateFormatter() {
10943
10943
  }, []);
10944
10944
  return {
10945
10945
  formatDate,
10946
- formatTime: formatTime3,
10946
+ formatTime: formatTime4,
10947
10947
  formatDateTime,
10948
10948
  getNow,
10949
10949
  timezone: defaultTimezone || "UTC",
@@ -23929,7 +23929,7 @@ var HourlyOutputChartComponent = ({
23929
23929
  endHour = Math.floor(endDecimalHour) % 24;
23930
23930
  endMinute = Math.round(endDecimalHour % 1 * 60);
23931
23931
  }
23932
- const formatTime3 = (h, m) => {
23932
+ const formatTime4 = (h, m) => {
23933
23933
  const period = h >= 12 ? "PM" : "AM";
23934
23934
  const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
23935
23935
  if (m === 0) {
@@ -23937,7 +23937,7 @@ var HourlyOutputChartComponent = ({
23937
23937
  }
23938
23938
  return `${hour12}:${m.toString().padStart(2, "0")}${period}`;
23939
23939
  };
23940
- return `${formatTime3(startHour, startMinute)}-${formatTime3(endHour, endMinute)}`;
23940
+ return `${formatTime4(startHour, startMinute)}-${formatTime4(endHour, endMinute)}`;
23941
23941
  }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
23942
23942
  const formatTimeRange = React23__namespace.default.useCallback((hourIndex) => {
23943
23943
  const isLastHour = hourIndex === SHIFT_DURATION - 1;
@@ -23953,12 +23953,12 @@ var HourlyOutputChartComponent = ({
23953
23953
  endHour = Math.floor(endDecimalHour) % 24;
23954
23954
  endMinute = Math.round(endDecimalHour % 1 * 60);
23955
23955
  }
23956
- const formatTime3 = (h, m) => {
23956
+ const formatTime4 = (h, m) => {
23957
23957
  const period = h >= 12 ? "PM" : "AM";
23958
23958
  const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
23959
23959
  return `${hour12}:${m.toString().padStart(2, "0")} ${period}`;
23960
23960
  };
23961
- return `${formatTime3(startHour, startMinute)} - ${formatTime3(endHour, endMinute)}`;
23961
+ return `${formatTime4(startHour, startMinute)} - ${formatTime4(endHour, endMinute)}`;
23962
23962
  }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
23963
23963
  const chartData = React23__namespace.default.useMemo(() => {
23964
23964
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
@@ -25129,7 +25129,7 @@ var SOPComplianceChart = ({
25129
25129
  }
25130
25130
  };
25131
25131
  }, [data, animateToNewData, mockData]);
25132
- const formatTime3 = (minuteIndex) => {
25132
+ const formatTime4 = (minuteIndex) => {
25133
25133
  const totalMinutes = shiftStartHour * 60 + minuteIndex;
25134
25134
  const hours = Math.floor(totalMinutes / 60) % 24;
25135
25135
  const minutes = totalMinutes % 60;
@@ -25141,7 +25141,7 @@ var SOPComplianceChart = ({
25141
25141
  const hasDataForMinute = index < animatedData.length - 10;
25142
25142
  return {
25143
25143
  minute: index,
25144
- time: formatTime3(index),
25144
+ time: formatTime4(index),
25145
25145
  compliance: hasDataForMinute ? animatedData[index] : null
25146
25146
  };
25147
25147
  });
@@ -25575,7 +25575,7 @@ var DateTimeDisplay = ({
25575
25575
  const {
25576
25576
  defaultTimezone
25577
25577
  } = useDateTimeConfig();
25578
- const { formatDate, formatTime: formatTime3 } = useDateFormatter();
25578
+ const { formatDate, formatTime: formatTime4 } = useDateFormatter();
25579
25579
  const [now2, setNow] = React23.useState(() => getCurrentTimeInZone(defaultTimezone || "UTC"));
25580
25580
  React23.useEffect(() => {
25581
25581
  const timerId = setInterval(() => {
@@ -25587,7 +25587,7 @@ var DateTimeDisplay = ({
25587
25587
  return null;
25588
25588
  }
25589
25589
  const formattedDate = showDate ? formatDate(now2) : "";
25590
- const formattedTime = showTime ? formatTime3(now2) : "";
25590
+ const formattedTime = showTime ? formatTime4(now2) : "";
25591
25591
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx_default("flex items-center space-x-2 text-sm text-gray-700 dark:text-gray-300", className), children: [
25592
25592
  showDate && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "date-display", "aria-label": `Current date: ${formattedDate}`, children: formattedDate }),
25593
25593
  showDate && showTime && formattedDate && formattedTime && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "separator", "aria-hidden": "true", children: "|" }),
@@ -25751,7 +25751,7 @@ var BreakNotificationPopup = ({
25751
25751
  const handlePrevious = () => {
25752
25752
  setCurrentIndex((prev) => (prev - 1 + visibleBreaks.length) % visibleBreaks.length);
25753
25753
  };
25754
- const formatTime3 = (minutes) => {
25754
+ const formatTime4 = (minutes) => {
25755
25755
  const hours = Math.floor(minutes / 60);
25756
25756
  const mins = minutes % 60;
25757
25757
  if (hours > 0) {
@@ -25825,9 +25825,9 @@ var BreakNotificationPopup = ({
25825
25825
  formatTo12Hour(currentBreak.endTime)
25826
25826
  ] }),
25827
25827
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 mb-2", children: [
25828
- formatTime3(currentBreak.elapsedMinutes),
25828
+ formatTime4(currentBreak.elapsedMinutes),
25829
25829
  " elapsed of ",
25830
- formatTime3(currentBreak.duration),
25830
+ formatTime4(currentBreak.duration),
25831
25831
  " total"
25832
25832
  ] }),
25833
25833
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-gray-200 rounded-full h-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -26131,64 +26131,208 @@ var getSeverityColor = (severity) => {
26131
26131
  return "bg-gray-500";
26132
26132
  }
26133
26133
  };
26134
- var PlayPauseIndicator = ({
26135
- show,
26134
+ var formatTime2 = (seconds) => {
26135
+ if (!seconds || isNaN(seconds)) return "0:00";
26136
+ const h = Math.floor(seconds / 3600);
26137
+ const m = Math.floor(seconds % 3600 / 60);
26138
+ const s = Math.floor(seconds % 60);
26139
+ if (h > 0) {
26140
+ return `${h}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
26141
+ }
26142
+ return `${m}:${s.toString().padStart(2, "0")}`;
26143
+ };
26144
+ var VideoControls = ({
26136
26145
  isPlaying,
26137
- duration = 600
26146
+ currentTime,
26147
+ duration,
26148
+ buffered,
26149
+ showControls,
26150
+ controlsPinned = false,
26151
+ onTogglePinControls,
26152
+ playbackRate = 1,
26153
+ onPlayPause,
26154
+ onSeek,
26155
+ onSeekStart,
26156
+ onSeekEnd,
26157
+ onToggleFullscreen,
26158
+ onPlaybackRateChange,
26159
+ className = ""
26138
26160
  }) => {
26139
- const [isVisible, setIsVisible] = React23.useState(false);
26140
- const [isFading, setIsFading] = React23.useState(false);
26161
+ const [isDragging, setIsDragging] = React23.useState(false);
26162
+ const [dragTime, setDragTime] = React23.useState(0);
26163
+ const [isHoveringProgressBar, setIsHoveringProgressBar] = React23.useState(false);
26164
+ const [showSpeedMenu, setShowSpeedMenu] = React23.useState(false);
26165
+ const speedMenuRef = React23.useRef(null);
26166
+ const progressColor = "#4b5563";
26167
+ const controlsVisible = showControls || controlsPinned;
26168
+ const getPercentage = (current, total) => {
26169
+ if (!total || total === 0) return 0;
26170
+ return Math.min(Math.max(current / total * 100, 0), 100);
26171
+ };
26172
+ const handleSeekChange = (e) => {
26173
+ const newTime = parseFloat(e.target.value);
26174
+ setDragTime(newTime);
26175
+ onSeek(newTime);
26176
+ };
26177
+ const handleSeekStart = () => {
26178
+ setIsDragging(true);
26179
+ setDragTime(currentTime);
26180
+ onSeekStart?.();
26181
+ };
26182
+ const handleSeekEnd = () => {
26183
+ setIsDragging(false);
26184
+ onSeekEnd?.();
26185
+ };
26141
26186
  React23.useEffect(() => {
26142
- if (show) {
26143
- setIsVisible(true);
26144
- setIsFading(false);
26145
- const fadeTimer = setTimeout(() => {
26146
- setIsFading(true);
26147
- }, 100);
26148
- const hideTimer = setTimeout(() => {
26149
- setIsVisible(false);
26150
- setIsFading(false);
26151
- }, duration);
26152
- return () => {
26153
- clearTimeout(fadeTimer);
26154
- clearTimeout(hideTimer);
26155
- };
26156
- }
26157
- }, [show, duration]);
26158
- if (!isVisible) return null;
26159
- return /* @__PURE__ */ jsxRuntime.jsx(
26187
+ const handleClickOutside = (event) => {
26188
+ if (speedMenuRef.current && !speedMenuRef.current.contains(event.target)) {
26189
+ setShowSpeedMenu(false);
26190
+ }
26191
+ };
26192
+ document.addEventListener("mousedown", handleClickOutside);
26193
+ return () => {
26194
+ document.removeEventListener("mousedown", handleClickOutside);
26195
+ };
26196
+ }, []);
26197
+ const displayTime = isDragging ? dragTime : currentTime;
26198
+ const progressPercent = getPercentage(displayTime, duration);
26199
+ const bufferedPercent = getPercentage(buffered, duration);
26200
+ return /* @__PURE__ */ jsxRuntime.jsxs(
26160
26201
  "div",
26161
26202
  {
26162
- className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
26163
- style: {
26164
- opacity: isFading ? 0 : 1,
26165
- transition: `opacity ${duration - 100}ms ease-out`
26166
- },
26167
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
26168
- // Play icon (triangle)
26169
- /* @__PURE__ */ jsxRuntime.jsx(
26170
- "svg",
26171
- {
26172
- xmlns: "http://www.w3.org/2000/svg",
26173
- viewBox: "0 0 24 24",
26174
- fill: "white",
26175
- className: "w-16 h-16",
26176
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 5v14l11-7z" })
26177
- }
26178
- )
26179
- ) : (
26180
- // Pause icon (two bars)
26181
- /* @__PURE__ */ jsxRuntime.jsx(
26182
- "svg",
26203
+ className: `absolute bottom-0 left-0 right-0 px-3 pb-3 pt-12 bg-gradient-to-t from-black/80 via-black/40 to-transparent transition-opacity duration-300 ${controlsVisible ? "opacity-100" : "opacity-0 pointer-events-none"} ${className}`,
26204
+ style: { touchAction: "none" },
26205
+ children: [
26206
+ /* @__PURE__ */ jsxRuntime.jsxs(
26207
+ "div",
26183
26208
  {
26184
- xmlns: "http://www.w3.org/2000/svg",
26185
- viewBox: "0 0 24 24",
26186
- fill: "white",
26187
- className: "w-16 h-16",
26188
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
26209
+ className: "relative h-1 mb-4 group cursor-pointer",
26210
+ onMouseEnter: () => setIsHoveringProgressBar(true),
26211
+ onMouseLeave: () => setIsHoveringProgressBar(false),
26212
+ children: [
26213
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute -top-2 -bottom-2 left-0 right-0 z-20" }),
26214
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 left-0 right-0 bottom-0 bg-white/20 rounded-full overflow-hidden z-0", children: /* @__PURE__ */ jsxRuntime.jsx(
26215
+ "div",
26216
+ {
26217
+ className: "absolute top-0 left-0 bottom-0 bg-white/40 transition-all duration-200",
26218
+ style: { width: `${bufferedPercent}%` }
26219
+ }
26220
+ ) }),
26221
+ /* @__PURE__ */ jsxRuntime.jsx(
26222
+ "div",
26223
+ {
26224
+ className: "absolute top-0 left-0 bottom-0 bg-[#007bff] transition-all duration-75 z-10",
26225
+ style: { width: `${progressPercent}%`, backgroundColor: progressColor },
26226
+ children: /* @__PURE__ */ jsxRuntime.jsx(
26227
+ "div",
26228
+ {
26229
+ className: `absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 w-3 h-3 rounded-full shadow transform transition-transform duration-200 ${isHoveringProgressBar || isDragging ? "scale-100" : "scale-0"}`,
26230
+ style: { backgroundColor: progressColor }
26231
+ }
26232
+ )
26233
+ }
26234
+ ),
26235
+ /* @__PURE__ */ jsxRuntime.jsx(
26236
+ "input",
26237
+ {
26238
+ type: "range",
26239
+ min: "0",
26240
+ max: duration || 100,
26241
+ step: "0.1",
26242
+ value: displayTime,
26243
+ onChange: handleSeekChange,
26244
+ onMouseDown: handleSeekStart,
26245
+ onMouseUp: handleSeekEnd,
26246
+ onTouchStart: handleSeekStart,
26247
+ onTouchEnd: handleSeekEnd,
26248
+ className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer z-30 margin-0 padding-0"
26249
+ }
26250
+ )
26251
+ ]
26189
26252
  }
26190
- )
26191
- ) })
26253
+ ),
26254
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-white", children: [
26255
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
26256
+ /* @__PURE__ */ jsxRuntime.jsx(
26257
+ "button",
26258
+ {
26259
+ onClick: (e) => {
26260
+ e.stopPropagation();
26261
+ onPlayPause();
26262
+ },
26263
+ className: "hover:text-[#007bff] transition-colors focus:outline-none",
26264
+ "aria-label": isPlaying ? "Pause" : "Play",
26265
+ children: isPlaying ? /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" }) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 5v14l11-7z" }) })
26266
+ }
26267
+ ),
26268
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs font-medium font-sans", children: [
26269
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime2(displayTime) }),
26270
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mx-1 text-white/70", children: "/" }),
26271
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white/70", children: formatTime2(duration) })
26272
+ ] })
26273
+ ] }),
26274
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
26275
+ onTogglePinControls && /* @__PURE__ */ jsxRuntime.jsx(
26276
+ "button",
26277
+ {
26278
+ onClick: (e) => {
26279
+ e.stopPropagation();
26280
+ onTogglePinControls();
26281
+ },
26282
+ className: `transition-colors focus:outline-none ${controlsPinned ? "text-[#007bff]" : "hover:text-[#007bff]"}`,
26283
+ "aria-label": controlsPinned ? "Unpin controls" : "Pin controls",
26284
+ title: controlsPinned ? "Unpin controls" : "Pin controls",
26285
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: controlsPinned ? /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 3h6l-1 7h3v2h-4.5l-.5 4.5-2 1L10 12H6v-2h3z" }) : /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 3h6l-1 7h3v2h-4v5l-2 1-1-6H6v-2h3z" }) })
26286
+ }
26287
+ ),
26288
+ onPlaybackRateChange && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", ref: speedMenuRef, children: [
26289
+ /* @__PURE__ */ jsxRuntime.jsxs(
26290
+ "button",
26291
+ {
26292
+ onClick: (e) => {
26293
+ e.stopPropagation();
26294
+ setShowSpeedMenu(!showSpeedMenu);
26295
+ },
26296
+ className: "text-xs font-medium hover:text-[#007bff] transition-colors focus:outline-none min-w-[32px]",
26297
+ "aria-label": "Playback Speed",
26298
+ children: [
26299
+ playbackRate,
26300
+ "x"
26301
+ ]
26302
+ }
26303
+ ),
26304
+ showSpeedMenu && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full right-0 mb-2 bg-black/90 text-white rounded shadow-lg overflow-hidden z-50 min-w-[80px]", children: [0.5, 1, 1.5, 2, 2.5, 3, 4, 5].map((rate) => /* @__PURE__ */ jsxRuntime.jsxs(
26305
+ "button",
26306
+ {
26307
+ onClick: (e) => {
26308
+ e.stopPropagation();
26309
+ onPlaybackRateChange(rate);
26310
+ setShowSpeedMenu(false);
26311
+ },
26312
+ className: `block w-full text-left px-4 py-2 text-xs hover:bg-white/20 transition-colors ${playbackRate === rate ? "text-[#007bff] font-bold" : ""}`,
26313
+ children: [
26314
+ rate,
26315
+ "x"
26316
+ ]
26317
+ },
26318
+ rate
26319
+ )) })
26320
+ ] }),
26321
+ onToggleFullscreen && /* @__PURE__ */ jsxRuntime.jsx(
26322
+ "button",
26323
+ {
26324
+ onClick: (e) => {
26325
+ e.stopPropagation();
26326
+ onToggleFullscreen();
26327
+ },
26328
+ className: "hover:text-[#007bff] transition-colors focus:outline-none",
26329
+ "aria-label": "Toggle Fullscreen",
26330
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
26331
+ }
26332
+ )
26333
+ ] })
26334
+ ] })
26335
+ ]
26192
26336
  }
26193
26337
  );
26194
26338
  };
@@ -26316,9 +26460,15 @@ var HlsVideoPlayer = React23.forwardRef(({
26316
26460
  const blobUrlRef = React23.useRef(null);
26317
26461
  const [isReady, setIsReady] = React23.useState(false);
26318
26462
  const [isLoading, setIsLoading] = React23.useState(true);
26319
- const [showIndicator, setShowIndicator] = React23.useState(false);
26320
- const [indicatorIsPlaying, setIndicatorIsPlaying] = React23.useState(false);
26321
- const indicatorKeyRef = React23.useRef(0);
26463
+ const [showControls, setShowControls] = React23.useState(true);
26464
+ const [controlsPinned, setControlsPinned] = React23.useState(false);
26465
+ const [isPlaying, setIsPlaying] = React23.useState(false);
26466
+ const [currentTime, setCurrentTime] = React23.useState(0);
26467
+ const [duration, setDuration] = React23.useState(0);
26468
+ const [buffered, setBuffered] = React23.useState(0);
26469
+ const [playbackRate, setPlaybackRate] = React23.useState(1);
26470
+ const userSeekingRef = React23.useRef(false);
26471
+ const controlsTimeoutRef = React23.useRef(null);
26322
26472
  const eventCallbacksRef = React23.useRef({
26323
26473
  onReady,
26324
26474
  onPlay,
@@ -26497,8 +26647,6 @@ var HlsVideoPlayer = React23.forwardRef(({
26497
26647
  }
26498
26648
  });
26499
26649
  hls.on(Hls3.Events.FRAG_LOADING, () => {
26500
- setIsLoading(true);
26501
- eventCallbacksRef.current.onLoadingChange?.(true);
26502
26650
  });
26503
26651
  hls.on(Hls3.Events.FRAG_LOADED, () => {
26504
26652
  setIsLoading(false);
@@ -26552,25 +26700,52 @@ var HlsVideoPlayer = React23.forwardRef(({
26552
26700
  const handleCanPlay = () => {
26553
26701
  if (!hlsRef.current) {
26554
26702
  setIsReady(true);
26555
- onReady?.(player);
26556
26703
  }
26704
+ setIsLoading(false);
26705
+ eventCallbacksRef.current.onLoadingChange?.(false);
26706
+ onReady?.(player);
26707
+ };
26708
+ const handlePlay = () => {
26709
+ setIsPlaying(true);
26710
+ eventCallbacksRef.current.onPlay?.(player);
26711
+ };
26712
+ const handlePause = () => {
26713
+ if (userSeekingRef.current && videoRef.current) {
26714
+ videoRef.current.play().catch((err) => console.warn("Auto-resume after seek pause failed:", err));
26715
+ return;
26716
+ }
26717
+ setIsPlaying(false);
26718
+ eventCallbacksRef.current.onPause?.(player);
26557
26719
  };
26558
- const handlePlay = () => eventCallbacksRef.current.onPlay?.(player);
26559
- const handlePause = () => eventCallbacksRef.current.onPause?.(player);
26560
26720
  const handlePlaying = () => {
26561
26721
  setIsLoading(false);
26722
+ setIsPlaying(true);
26562
26723
  eventCallbacksRef.current.onLoadingChange?.(false);
26563
26724
  eventCallbacksRef.current.onPlaying?.(player);
26564
26725
  };
26565
26726
  const handleTimeUpdate = () => {
26566
26727
  const currentTime2 = video.currentTime || 0;
26728
+ setCurrentTime(currentTime2);
26729
+ if (video.buffered.length > 0) {
26730
+ for (let i = 0; i < video.buffered.length; i++) {
26731
+ if (video.buffered.start(i) <= currentTime2 && video.buffered.end(i) >= currentTime2) {
26732
+ setBuffered(video.buffered.end(i));
26733
+ break;
26734
+ }
26735
+ }
26736
+ }
26567
26737
  eventCallbacksRef.current.onTimeUpdate?.(player, currentTime2);
26568
26738
  };
26569
26739
  const handleDurationChange = () => {
26570
26740
  const duration2 = video.duration || 0;
26741
+ setDuration(duration2);
26571
26742
  eventCallbacksRef.current.onDurationChange?.(player, duration2);
26572
26743
  };
26573
- const handleEnded = () => eventCallbacksRef.current.onEnded?.(player);
26744
+ const handleEnded = () => {
26745
+ setIsPlaying(false);
26746
+ userSeekingRef.current = false;
26747
+ eventCallbacksRef.current.onEnded?.(player);
26748
+ };
26574
26749
  const handleLoadStart = () => {
26575
26750
  setIsLoading(true);
26576
26751
  eventCallbacksRef.current.onLoadingChange?.(true);
@@ -26586,8 +26761,19 @@ var HlsVideoPlayer = React23.forwardRef(({
26586
26761
  setIsLoading(true);
26587
26762
  eventCallbacksRef.current.onLoadingChange?.(true);
26588
26763
  };
26589
- const handleSeeking = () => eventCallbacksRef.current.onSeeking?.(player);
26590
- const handleSeeked = () => eventCallbacksRef.current.onSeeked?.(player);
26764
+ const handleSeeking = () => {
26765
+ userSeekingRef.current = true;
26766
+ eventCallbacksRef.current.onSeeking?.(player);
26767
+ };
26768
+ const handleSeeked = () => {
26769
+ setIsLoading(false);
26770
+ eventCallbacksRef.current.onLoadingChange?.(false);
26771
+ if (videoRef.current) {
26772
+ videoRef.current.play().catch((err) => console.warn("Resume playback after seek failed:", err));
26773
+ }
26774
+ userSeekingRef.current = false;
26775
+ eventCallbacksRef.current.onSeeked?.(player);
26776
+ };
26591
26777
  const handleError = () => {
26592
26778
  const error = video.error;
26593
26779
  if (error) {
@@ -26600,6 +26786,10 @@ var HlsVideoPlayer = React23.forwardRef(({
26600
26786
  eventCallbacksRef.current.onError?.(player, errorInfo);
26601
26787
  }
26602
26788
  };
26789
+ const handlePlaybackRateChange2 = (e) => {
26790
+ const target = e.target;
26791
+ setPlaybackRate(target.playbackRate);
26792
+ };
26603
26793
  video.addEventListener("canplay", handleCanPlay);
26604
26794
  video.addEventListener("play", handlePlay);
26605
26795
  video.addEventListener("pause", handlePause);
@@ -26614,6 +26804,7 @@ var HlsVideoPlayer = React23.forwardRef(({
26614
26804
  video.addEventListener("seeking", handleSeeking);
26615
26805
  video.addEventListener("seeked", handleSeeked);
26616
26806
  video.addEventListener("error", handleError);
26807
+ video.addEventListener("ratechange", handlePlaybackRateChange2);
26617
26808
  return () => {
26618
26809
  video.removeEventListener("canplay", handleCanPlay);
26619
26810
  video.removeEventListener("play", handlePlay);
@@ -26629,6 +26820,7 @@ var HlsVideoPlayer = React23.forwardRef(({
26629
26820
  video.removeEventListener("seeking", handleSeeking);
26630
26821
  video.removeEventListener("seeked", handleSeeked);
26631
26822
  video.removeEventListener("error", handleError);
26823
+ video.removeEventListener("ratechange", handlePlaybackRateChange2);
26632
26824
  };
26633
26825
  }, [
26634
26826
  src,
@@ -26657,20 +26849,46 @@ var HlsVideoPlayer = React23.forwardRef(({
26657
26849
  }
26658
26850
  }
26659
26851
  }, [autoplay]);
26852
+ const resetControlsTimeout = React23.useCallback(() => {
26853
+ if (controlsPinned) {
26854
+ setShowControls(true);
26855
+ return;
26856
+ }
26857
+ setShowControls(true);
26858
+ if (controlsTimeoutRef.current) {
26859
+ clearTimeout(controlsTimeoutRef.current);
26860
+ }
26861
+ if (isPlaying) {
26862
+ controlsTimeoutRef.current = setTimeout(() => {
26863
+ setShowControls(false);
26864
+ }, 3e3);
26865
+ }
26866
+ }, [isPlaying, controlsPinned]);
26867
+ const handleMouseMove = React23.useCallback(() => {
26868
+ resetControlsTimeout();
26869
+ }, [resetControlsTimeout]);
26870
+ React23.useEffect(() => {
26871
+ resetControlsTimeout();
26872
+ return () => {
26873
+ if (controlsTimeoutRef.current) {
26874
+ clearTimeout(controlsTimeoutRef.current);
26875
+ }
26876
+ };
26877
+ }, [isPlaying, resetControlsTimeout]);
26660
26878
  const play = React23.useCallback(() => {
26661
26879
  return videoRef.current?.play();
26662
26880
  }, []);
26663
26881
  const pause = React23.useCallback(() => {
26664
26882
  videoRef.current?.pause();
26665
26883
  }, []);
26666
- const currentTime = React23.useCallback((time2) => {
26884
+ const currentTimeProp = React23.useCallback((time2) => {
26667
26885
  if (time2 !== void 0 && videoRef.current) {
26668
26886
  videoRef.current.currentTime = time2;
26669
26887
  return time2;
26670
26888
  }
26671
26889
  return videoRef.current?.currentTime || 0;
26672
26890
  }, []);
26673
- const duration = React23.useCallback(() => {
26891
+ const durationProp = React23.useCallback(() => {
26674
26892
  return videoRef.current?.duration || 0;
26675
26893
  }, []);
26676
26894
  const paused = React23.useCallback(() => {
@@ -26683,95 +26901,144 @@ var HlsVideoPlayer = React23.forwardRef(({
26683
26901
  }
26684
26902
  return videoRef.current?.muted ?? false;
26685
26903
  }, []);
26686
- const volume = React23.useCallback((level) => {
26904
+ const volumeProp = React23.useCallback((level) => {
26687
26905
  if (level !== void 0 && videoRef.current) {
26688
26906
  videoRef.current.volume = level;
26689
26907
  return level;
26690
26908
  }
26691
26909
  return videoRef.current?.volume ?? 1;
26692
26910
  }, []);
26693
- const playbackRate = React23.useCallback((rate) => {
26911
+ const playbackRateProp = React23.useCallback((rate) => {
26694
26912
  if (rate !== void 0 && videoRef.current) {
26695
26913
  videoRef.current.playbackRate = rate;
26696
26914
  return rate;
26697
26915
  }
26698
26916
  return videoRef.current?.playbackRate ?? 1;
26699
26917
  }, []);
26918
+ const handleTogglePlay = React23.useCallback(() => {
26919
+ if (videoRef.current) {
26920
+ if (videoRef.current.paused) {
26921
+ videoRef.current.play();
26922
+ } else {
26923
+ videoRef.current.pause();
26924
+ }
26925
+ }
26926
+ }, []);
26927
+ const handleSeek = React23.useCallback((time2) => {
26928
+ if (videoRef.current) {
26929
+ videoRef.current.currentTime = time2;
26930
+ videoRef.current.play().catch((err) => console.warn("Resume playback failed during seek:", err));
26931
+ }
26932
+ }, []);
26933
+ const handleSeekStart = React23.useCallback(() => {
26934
+ userSeekingRef.current = true;
26935
+ }, []);
26936
+ const handleSeekEnd = React23.useCallback(() => {
26937
+ if (videoRef.current) {
26938
+ videoRef.current.play().catch((err) => console.warn("Resume playback failed after seek:", err));
26939
+ }
26940
+ }, []);
26941
+ const handlePlaybackRateChange = React23.useCallback((rate) => {
26942
+ if (videoRef.current) {
26943
+ videoRef.current.playbackRate = rate;
26944
+ }
26945
+ }, []);
26946
+ const handleToggleFullscreen = React23.useCallback(() => {
26947
+ if (videoContainerRef.current) {
26948
+ if (!document.fullscreenElement) {
26949
+ videoContainerRef.current.requestFullscreen();
26950
+ } else {
26951
+ document.exitFullscreen();
26952
+ }
26953
+ }
26954
+ }, []);
26700
26955
  React23.useImperativeHandle(ref, () => ({
26701
26956
  hls: hlsRef.current,
26702
26957
  video: videoRef.current,
26703
26958
  play,
26704
26959
  pause,
26705
- currentTime,
26706
- duration,
26960
+ currentTime: currentTimeProp,
26961
+ duration: durationProp,
26707
26962
  paused,
26708
26963
  mute,
26709
- volume,
26710
- playbackRate,
26964
+ volume: volumeProp,
26965
+ playbackRate: playbackRateProp,
26711
26966
  dispose,
26712
26967
  isReady,
26713
26968
  // For backward compatibility with Video.js API
26714
26969
  player: playerLikeObject()
26715
- }), [play, pause, currentTime, duration, paused, mute, volume, playbackRate, dispose, isReady, playerLikeObject]);
26716
- const handleClickWithIndicator = React23.useCallback(() => {
26717
- if (!onClick || !videoRef.current) return;
26718
- const willBePlaying = videoRef.current.paused;
26719
- setIndicatorIsPlaying(willBePlaying);
26720
- setShowIndicator(false);
26721
- setTimeout(() => {
26722
- indicatorKeyRef.current += 1;
26723
- setShowIndicator(true);
26724
- }, 0);
26725
- onClick();
26726
- }, [onClick]);
26727
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `hls-video-player-wrapper ${className}`, style: { position: "relative", width: "100%", height: "100%" }, children: [
26728
- /* @__PURE__ */ jsxRuntime.jsx(
26729
- "div",
26730
- {
26731
- className: "hls-video-player-container",
26732
- ref: videoContainerRef,
26733
- children: /* @__PURE__ */ jsxRuntime.jsx(
26734
- "video",
26970
+ }), [play, pause, currentTimeProp, durationProp, paused, mute, volumeProp, playbackRateProp, dispose, isReady, playerLikeObject]);
26971
+ const handleContainerClick = React23.useCallback(() => {
26972
+ if (!onClick && !controls) {
26973
+ handleTogglePlay();
26974
+ }
26975
+ if (onClick) {
26976
+ onClick();
26977
+ }
26978
+ }, [onClick, controls, handleTogglePlay]);
26979
+ return /* @__PURE__ */ jsxRuntime.jsxs(
26980
+ "div",
26981
+ {
26982
+ className: `hls-video-player-wrapper ${className} group`,
26983
+ style: { position: "relative", width: "100%", height: "100%" },
26984
+ onMouseMove: handleMouseMove,
26985
+ onMouseLeave: () => isPlaying && !controlsPinned && setShowControls(false),
26986
+ children: [
26987
+ /* @__PURE__ */ jsxRuntime.jsxs(
26988
+ "div",
26735
26989
  {
26736
- ref: videoRef,
26737
- className: "hls-video-element",
26738
- poster,
26739
- controls,
26740
- loop,
26741
- muted,
26742
- playsInline,
26743
- autoPlay: autoplay,
26744
- preload: "metadata"
26990
+ className: "hls-video-player-container",
26991
+ ref: videoContainerRef,
26992
+ onClick: handleContainerClick,
26993
+ children: [
26994
+ /* @__PURE__ */ jsxRuntime.jsx(
26995
+ "video",
26996
+ {
26997
+ ref: videoRef,
26998
+ className: "hls-video-element",
26999
+ poster,
27000
+ controls: false,
27001
+ loop,
27002
+ muted,
27003
+ playsInline,
27004
+ autoPlay: autoplay,
27005
+ preload: "metadata"
27006
+ }
27007
+ ),
27008
+ controls && /* @__PURE__ */ jsxRuntime.jsx(
27009
+ VideoControls,
27010
+ {
27011
+ isPlaying,
27012
+ currentTime,
27013
+ duration,
27014
+ buffered,
27015
+ showControls: controlsPinned || showControls || !isPlaying,
27016
+ controlsPinned,
27017
+ playbackRate,
27018
+ onPlayPause: handleTogglePlay,
27019
+ onSeek: handleSeek,
27020
+ onSeekStart: handleSeekStart,
27021
+ onSeekEnd: handleSeekEnd,
27022
+ onPlaybackRateChange: handlePlaybackRateChange,
27023
+ onTogglePinControls: () => setControlsPinned((prev) => {
27024
+ const next = !prev;
27025
+ if (next) {
27026
+ setShowControls(true);
27027
+ } else {
27028
+ resetControlsTimeout();
27029
+ }
27030
+ return next;
27031
+ }),
27032
+ onToggleFullscreen: handleToggleFullscreen
27033
+ }
27034
+ )
27035
+ ]
26745
27036
  }
26746
- )
26747
- }
26748
- ),
26749
- isLoading && !externalLoadingControl && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
26750
- onClick && !controls && /* @__PURE__ */ jsxRuntime.jsx(
26751
- "div",
26752
- {
26753
- onClick: handleClickWithIndicator,
26754
- style: {
26755
- position: "absolute",
26756
- top: 0,
26757
- left: 0,
26758
- right: 0,
26759
- bottom: 0,
26760
- zIndex: 1,
26761
- cursor: "pointer"
26762
- },
26763
- "aria-label": "Click to play/pause"
26764
- }
26765
- ),
26766
- onClick && !controls && /* @__PURE__ */ jsxRuntime.jsx(
26767
- PlayPauseIndicator,
26768
- {
26769
- show: showIndicator,
26770
- isPlaying: indicatorIsPlaying
26771
- },
26772
- indicatorKeyRef.current
26773
- )
26774
- ] });
27037
+ ),
27038
+ isLoading && !externalLoadingControl && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) })
27039
+ ]
27040
+ }
27041
+ );
26775
27042
  });
26776
27043
  HlsVideoPlayer.displayName = "HlsVideoPlayer";
26777
27044
  var VideoPlayer = HlsVideoPlayer;
@@ -26779,6 +27046,7 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
26779
27046
  crop,
26780
27047
  debug = false,
26781
27048
  onClick,
27049
+ controls = true,
26782
27050
  ...videoProps
26783
27051
  }, ref) => {
26784
27052
  const {
@@ -26800,9 +27068,15 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
26800
27068
  const [isVideoReady, setIsVideoReady] = React23.useState(false);
26801
27069
  const [canvasDimensions, setCanvasDimensions] = React23.useState({ width: 0, height: 0 });
26802
27070
  const [isProcessing, setIsProcessing] = React23.useState(false);
26803
- const [showIndicator, setShowIndicator] = React23.useState(false);
26804
- const [indicatorIsPlaying, setIndicatorIsPlaying] = React23.useState(false);
26805
- const indicatorKeyRef = React23.useRef(0);
27071
+ const [showControls, setShowControls] = React23.useState(true);
27072
+ const [isPlaying, setIsPlaying] = React23.useState(false);
27073
+ const [currentTime, setCurrentTime] = React23.useState(0);
27074
+ const [duration, setDuration] = React23.useState(0);
27075
+ const [buffered, setBuffered] = React23.useState(0);
27076
+ const [playbackRate, setPlaybackRate] = React23.useState(1);
27077
+ const controlsTimeoutRef = React23.useRef(null);
27078
+ const userSeekingRef = React23.useRef(false);
27079
+ const [controlsPinned, setControlsPinned] = React23.useState(false);
26806
27080
  const stopCanvasRendering = React23.useCallback(() => {
26807
27081
  if (animationFrameRef.current) {
26808
27082
  cancelAnimationFrame(animationFrameRef.current);
@@ -26887,7 +27161,7 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
26887
27161
  const canvas = canvasRef.current;
26888
27162
  const video = videoElementRef.current;
26889
27163
  const ctx = canvas.getContext("2d");
26890
- if (!ctx || video.paused || video.ended) {
27164
+ if (!ctx || video.readyState < 2) {
26891
27165
  return;
26892
27166
  }
26893
27167
  const videoWidth = video.videoWidth;
@@ -26914,7 +27188,9 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
26914
27188
  canvas.height
26915
27189
  // Destination (full canvas)
26916
27190
  );
26917
- animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
27191
+ if (!video.paused && !video.ended) {
27192
+ animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
27193
+ }
26918
27194
  }, [crop]);
26919
27195
  const handleVideoReady = React23.useCallback((player) => {
26920
27196
  console.log("[CroppedHlsVideoPlayer] Video player ready");
@@ -26922,11 +27198,15 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
26922
27198
  if (videoEl) {
26923
27199
  videoElementRef.current = videoEl;
26924
27200
  setIsVideoReady(true);
27201
+ if (videoEl.readyState >= 2) {
27202
+ renderFrameToCanvas();
27203
+ }
26925
27204
  }
26926
27205
  onReadyProp?.(player);
26927
- }, [onReadyProp]);
27206
+ }, [onReadyProp, renderFrameToCanvas]);
26928
27207
  const handleVideoPlay = React23.useCallback((player) => {
26929
27208
  console.log("[CroppedHlsVideoPlayer] Video playing, starting canvas rendering");
27209
+ setIsPlaying(true);
26930
27210
  if (crop && canvasRef.current) {
26931
27211
  setIsProcessing(true);
26932
27212
  renderFrameToCanvas();
@@ -26934,43 +27214,40 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
26934
27214
  onPlayProp?.(player);
26935
27215
  }, [crop, renderFrameToCanvas, onPlayProp]);
26936
27216
  const handleVideoPause = React23.useCallback((player) => {
26937
- console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering and CLEARING canvas");
27217
+ console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering (keeping last frame)");
27218
+ if (userSeekingRef.current && hiddenVideoRef.current) {
27219
+ hiddenVideoRef.current.play()?.catch(() => {
27220
+ });
27221
+ return;
27222
+ }
26938
27223
  stopCanvasRendering();
26939
27224
  setIsProcessing(false);
26940
- if (canvasRef.current) {
26941
- const ctx = canvasRef.current.getContext("2d");
26942
- if (ctx) {
26943
- ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26944
- ctx.fillStyle = "black";
26945
- ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26946
- }
26947
- }
27225
+ setIsPlaying(false);
27226
+ renderFrameToCanvas();
26948
27227
  onPauseProp?.(player);
26949
- }, [stopCanvasRendering, onPauseProp]);
27228
+ }, [stopCanvasRendering, onPauseProp, renderFrameToCanvas]);
26950
27229
  const handleVideoEnded = React23.useCallback((player) => {
26951
- console.log("[CroppedHlsVideoPlayer] Video ended, CLEARING canvas");
27230
+ console.log("[CroppedHlsVideoPlayer] Video ended, stopping canvas rendering (keeping last frame)");
26952
27231
  stopCanvasRendering();
26953
27232
  setIsProcessing(false);
26954
- if (canvasRef.current) {
26955
- const ctx = canvasRef.current.getContext("2d");
26956
- if (ctx) {
26957
- ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26958
- ctx.fillStyle = "black";
26959
- ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26960
- }
26961
- }
27233
+ setIsPlaying(false);
27234
+ userSeekingRef.current = false;
26962
27235
  onEndedProp?.(player);
26963
27236
  }, [stopCanvasRendering, onEndedProp]);
26964
27237
  const handleSeeking = React23.useCallback((player) => {
26965
27238
  console.log("[CroppedHlsVideoPlayer] Video seeking");
26966
- if (crop && !videoElementRef.current?.paused) {
27239
+ userSeekingRef.current = true;
27240
+ if (crop) {
26967
27241
  renderFrameToCanvas();
26968
27242
  }
26969
27243
  onSeekingProp?.(player);
26970
27244
  }, [crop, renderFrameToCanvas, onSeekingProp]);
26971
27245
  const handleSeeked = React23.useCallback((player) => {
26972
27246
  console.log("[CroppedHlsVideoPlayer] Video seeked");
26973
- if (crop && !videoElementRef.current?.paused) {
27247
+ hiddenVideoRef.current?.play()?.catch(() => {
27248
+ });
27249
+ userSeekingRef.current = false;
27250
+ if (crop) {
26974
27251
  renderFrameToCanvas();
26975
27252
  }
26976
27253
  onSeekedProp?.(player);
@@ -26978,8 +27255,29 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
26978
27255
  const handleLoadedMetadata = React23.useCallback((player) => {
26979
27256
  console.log("[CroppedHlsVideoPlayer] Video metadata loaded");
26980
27257
  calculateCanvasDimensions();
27258
+ if (hiddenVideoRef.current?.video) {
27259
+ setDuration(hiddenVideoRef.current.video.duration || 0);
27260
+ }
27261
+ requestAnimationFrame(() => renderFrameToCanvas());
26981
27262
  onLoadedMetadataProp?.(player);
26982
- }, [calculateCanvasDimensions, onLoadedMetadataProp]);
27263
+ }, [calculateCanvasDimensions, onLoadedMetadataProp, renderFrameToCanvas]);
27264
+ const handleTimeUpdate = React23.useCallback((player, time2) => {
27265
+ setCurrentTime(time2);
27266
+ if (hiddenVideoRef.current?.video && hiddenVideoRef.current.video.buffered.length > 0) {
27267
+ const video = hiddenVideoRef.current.video;
27268
+ for (let i = 0; i < video.buffered.length; i++) {
27269
+ if (video.buffered.start(i) <= time2 && video.buffered.end(i) >= time2) {
27270
+ setBuffered(video.buffered.end(i));
27271
+ break;
27272
+ }
27273
+ }
27274
+ }
27275
+ videoProps.onTimeUpdate?.(player, time2);
27276
+ }, [videoProps.onTimeUpdate]);
27277
+ const handleDurationChange = React23.useCallback((player, dur) => {
27278
+ setDuration(dur);
27279
+ videoProps.onDurationChange?.(player, dur);
27280
+ }, [videoProps.onDurationChange]);
26983
27281
  React23.useEffect(() => {
26984
27282
  calculateCanvasDimensions();
26985
27283
  const handleResize = () => {
@@ -27007,33 +27305,97 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
27007
27305
  stopCanvasRendering();
27008
27306
  };
27009
27307
  }, [stopCanvasRendering]);
27308
+ const resetControlsTimeout = React23.useCallback(() => {
27309
+ if (controlsPinned) {
27310
+ setShowControls(true);
27311
+ return;
27312
+ }
27313
+ setShowControls(true);
27314
+ if (controlsTimeoutRef.current) {
27315
+ clearTimeout(controlsTimeoutRef.current);
27316
+ }
27317
+ if (isPlaying) {
27318
+ controlsTimeoutRef.current = setTimeout(() => {
27319
+ setShowControls(false);
27320
+ }, 3e3);
27321
+ }
27322
+ }, [isPlaying, controlsPinned]);
27323
+ const handleMouseMove = React23.useCallback(() => {
27324
+ resetControlsTimeout();
27325
+ }, [resetControlsTimeout]);
27326
+ React23.useEffect(() => {
27327
+ resetControlsTimeout();
27328
+ return () => {
27329
+ if (controlsTimeoutRef.current) {
27330
+ clearTimeout(controlsTimeoutRef.current);
27331
+ }
27332
+ };
27333
+ }, [isPlaying, resetControlsTimeout]);
27334
+ const handleTogglePlay = React23.useCallback(() => {
27335
+ if (hiddenVideoRef.current?.video) {
27336
+ if (hiddenVideoRef.current.video.paused) {
27337
+ hiddenVideoRef.current.play();
27338
+ } else {
27339
+ hiddenVideoRef.current.pause();
27340
+ }
27341
+ }
27342
+ }, []);
27343
+ const handleSeek = React23.useCallback((time2) => {
27344
+ if (hiddenVideoRef.current) {
27345
+ hiddenVideoRef.current.currentTime(time2);
27346
+ hiddenVideoRef.current.play()?.catch(() => {
27347
+ });
27348
+ setTimeout(() => renderFrameToCanvas(), 50);
27349
+ }
27350
+ }, [renderFrameToCanvas]);
27351
+ const handleSeekStart = React23.useCallback(() => {
27352
+ userSeekingRef.current = true;
27353
+ }, []);
27354
+ const handleSeekEnd = React23.useCallback(() => {
27355
+ if (hiddenVideoRef.current) {
27356
+ hiddenVideoRef.current.play()?.catch(() => {
27357
+ });
27358
+ }
27359
+ }, []);
27360
+ const handlePlaybackRateChange = React23.useCallback((rate) => {
27361
+ if (hiddenVideoRef.current) {
27362
+ hiddenVideoRef.current.playbackRate(rate);
27363
+ setPlaybackRate(rate);
27364
+ }
27365
+ }, []);
27366
+ const handleToggleFullscreen = React23.useCallback(() => {
27367
+ if (videoContainerRef.current) {
27368
+ if (!document.fullscreenElement) {
27369
+ videoContainerRef.current.requestFullscreen();
27370
+ } else {
27371
+ document.exitFullscreen();
27372
+ }
27373
+ }
27374
+ }, []);
27010
27375
  if (!crop) {
27011
- return /* @__PURE__ */ jsxRuntime.jsx(HlsVideoPlayer, { ref, ...videoProps, onClick });
27012
- }
27013
- const handleClickWithIndicator = () => {
27014
- if (!onClick || !hiddenVideoRef.current?.video) return;
27015
- const video = hiddenVideoRef.current.video;
27016
- const willBePlaying = video.paused;
27017
- setIndicatorIsPlaying(willBePlaying);
27018
- setShowIndicator(false);
27019
- setTimeout(() => {
27020
- indicatorKeyRef.current += 1;
27021
- setShowIndicator(true);
27022
- }, 0);
27023
- onClick();
27376
+ return /* @__PURE__ */ jsxRuntime.jsx(HlsVideoPlayer, { ref, ...videoProps, onClick, controls });
27377
+ }
27378
+ const handleClick = () => {
27379
+ if (!onClick && !controls) {
27380
+ handleTogglePlay();
27381
+ }
27382
+ if (onClick) onClick();
27024
27383
  };
27025
27384
  return /* @__PURE__ */ jsxRuntime.jsxs(
27026
27385
  "div",
27027
27386
  {
27028
27387
  ref: videoContainerRef,
27029
- className: `relative w-full h-full flex items-center justify-center bg-black ${onClick ? "cursor-pointer" : ""} ${inheritedClassName}`,
27030
- onClick: handleClickWithIndicator,
27388
+ className: `relative w-full h-full flex items-center justify-center bg-black group ${inheritedClassName} ${onClick || controls ? "cursor-pointer" : ""}`,
27389
+ onClick: handleClick,
27390
+ onMouseMove: handleMouseMove,
27391
+ onMouseLeave: () => isPlaying && !controlsPinned && setShowControls(false),
27031
27392
  children: [
27032
27393
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
27033
27394
  HlsVideoPlayer,
27034
27395
  {
27035
27396
  ref: hiddenVideoRef,
27036
27397
  ...videoProps,
27398
+ controls: false,
27037
27399
  onReady: handleVideoReady,
27038
27400
  onPlay: handleVideoPlay,
27039
27401
  onPause: handleVideoPause,
@@ -27043,7 +27405,9 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
27043
27405
  onLoadedMetadata: handleLoadedMetadata,
27044
27406
  onLoadedData: videoProps.onLoadedData,
27045
27407
  onPlaying: videoProps.onPlaying,
27046
- onLoadingChange: videoProps.onLoadingChange
27408
+ onLoadingChange: videoProps.onLoadingChange,
27409
+ onTimeUpdate: handleTimeUpdate,
27410
+ onDurationChange: handleDurationChange
27047
27411
  }
27048
27412
  ) }),
27049
27413
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -27060,8 +27424,8 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
27060
27424
  }
27061
27425
  }
27062
27426
  ),
27063
- !isVideoReady && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27064
- debug && isVideoReady && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono", children: [
27427
+ !isVideoReady && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27428
+ debug && isVideoReady && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono pointer-events-none z-20", children: [
27065
27429
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
27066
27430
  "Crop: ",
27067
27431
  crop.x,
@@ -27085,13 +27449,32 @@ var CroppedHlsVideoPlayer = React23.forwardRef(({
27085
27449
  isProcessing ? "Yes" : "No"
27086
27450
  ] })
27087
27451
  ] }),
27088
- onClick && /* @__PURE__ */ jsxRuntime.jsx(
27089
- PlayPauseIndicator,
27452
+ controls && isVideoReady && /* @__PURE__ */ jsxRuntime.jsx(
27453
+ VideoControls,
27090
27454
  {
27091
- show: showIndicator,
27092
- isPlaying: indicatorIsPlaying
27093
- },
27094
- indicatorKeyRef.current
27455
+ isPlaying,
27456
+ currentTime,
27457
+ duration,
27458
+ buffered,
27459
+ showControls: controlsPinned || showControls || !isPlaying,
27460
+ controlsPinned,
27461
+ playbackRate,
27462
+ onPlayPause: handleTogglePlay,
27463
+ onSeek: handleSeek,
27464
+ onSeekStart: handleSeekStart,
27465
+ onSeekEnd: handleSeekEnd,
27466
+ onPlaybackRateChange: handlePlaybackRateChange,
27467
+ onTogglePinControls: () => setControlsPinned((prev) => {
27468
+ const next = !prev;
27469
+ if (next) {
27470
+ setShowControls(true);
27471
+ } else {
27472
+ resetControlsTimeout();
27473
+ }
27474
+ return next;
27475
+ }),
27476
+ onToggleFullscreen: handleToggleFullscreen
27477
+ }
27095
27478
  )
27096
27479
  ]
27097
27480
  }
@@ -27630,6 +28013,67 @@ var SilentErrorBoundary = class extends React23__namespace.default.Component {
27630
28013
  ] }) });
27631
28014
  }
27632
28015
  };
28016
+ var PlayPauseIndicator = ({
28017
+ show,
28018
+ isPlaying,
28019
+ duration = 600
28020
+ }) => {
28021
+ const [isVisible, setIsVisible] = React23.useState(false);
28022
+ const [isFading, setIsFading] = React23.useState(false);
28023
+ React23.useEffect(() => {
28024
+ if (show) {
28025
+ setIsVisible(true);
28026
+ setIsFading(false);
28027
+ const fadeTimer = setTimeout(() => {
28028
+ setIsFading(true);
28029
+ }, 100);
28030
+ const hideTimer = setTimeout(() => {
28031
+ setIsVisible(false);
28032
+ setIsFading(false);
28033
+ }, duration);
28034
+ return () => {
28035
+ clearTimeout(fadeTimer);
28036
+ clearTimeout(hideTimer);
28037
+ };
28038
+ }
28039
+ }, [show, duration]);
28040
+ if (!isVisible) return null;
28041
+ return /* @__PURE__ */ jsxRuntime.jsx(
28042
+ "div",
28043
+ {
28044
+ className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
28045
+ style: {
28046
+ opacity: isFading ? 0 : 1,
28047
+ transition: `opacity ${duration - 100}ms ease-out`
28048
+ },
28049
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
28050
+ // Play icon (triangle)
28051
+ /* @__PURE__ */ jsxRuntime.jsx(
28052
+ "svg",
28053
+ {
28054
+ xmlns: "http://www.w3.org/2000/svg",
28055
+ viewBox: "0 0 24 24",
28056
+ fill: "white",
28057
+ className: "w-16 h-16",
28058
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 5v14l11-7z" })
28059
+ }
28060
+ )
28061
+ ) : (
28062
+ // Pause icon (two bars)
28063
+ /* @__PURE__ */ jsxRuntime.jsx(
28064
+ "svg",
28065
+ {
28066
+ xmlns: "http://www.w3.org/2000/svg",
28067
+ viewBox: "0 0 24 24",
28068
+ fill: "white",
28069
+ className: "w-16 h-16",
28070
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
28071
+ }
28072
+ )
28073
+ ) })
28074
+ }
28075
+ );
28076
+ };
27633
28077
  var BackButton = ({
27634
28078
  onClick,
27635
28079
  text = "Back",
@@ -31049,7 +31493,7 @@ function DiagnosisVideoModal({
31049
31493
  }
31050
31494
  loadClip();
31051
31495
  }, [clipId, supabase, transformPlaylistUrls]);
31052
- const formatTime3 = (seconds) => {
31496
+ const formatTime4 = (seconds) => {
31053
31497
  const mins = Math.floor(seconds / 60);
31054
31498
  const secs = Math.floor(seconds % 60);
31055
31499
  return `${mins}:${secs.toString().padStart(2, "0")}`;
@@ -31241,9 +31685,9 @@ function DiagnosisVideoModal({
31241
31685
  }
31242
31686
  ),
31243
31687
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-medium", children: [
31244
- formatTime3(currentTime),
31688
+ formatTime4(currentTime),
31245
31689
  " / ",
31246
- formatTime3(duration)
31690
+ formatTime4(duration)
31247
31691
  ] }),
31248
31692
  /* @__PURE__ */ jsxRuntime.jsx(
31249
31693
  "input",
@@ -33214,7 +33658,7 @@ var LinePdfGenerator = ({
33214
33658
  }
33215
33659
  hourEndTime.setSeconds(0);
33216
33660
  hourEndTime.setMilliseconds(0);
33217
- const formatTime3 = (date2) => {
33661
+ const formatTime4 = (date2) => {
33218
33662
  return date2.toLocaleTimeString("en-IN", {
33219
33663
  hour: "2-digit",
33220
33664
  minute: "2-digit",
@@ -33222,7 +33666,7 @@ var LinePdfGenerator = ({
33222
33666
  timeZone: "Asia/Kolkata"
33223
33667
  });
33224
33668
  };
33225
- return `${formatTime3(hourStartTime)} - ${formatTime3(hourEndTime)}`;
33669
+ return `${formatTime4(hourStartTime)} - ${formatTime4(hourEndTime)}`;
33226
33670
  });
33227
33671
  };
33228
33672
  const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
@@ -39808,7 +40252,7 @@ var AIAgentView = () => {
39808
40252
  }
39809
40253
  return formattedLines.join("");
39810
40254
  };
39811
- const formatTime3 = (timestamp) => {
40255
+ const formatTime4 = (timestamp) => {
39812
40256
  const date = new Date(timestamp);
39813
40257
  return date.toLocaleTimeString([], {
39814
40258
  hour: "2-digit",
@@ -41072,7 +41516,7 @@ var AIAgentView = () => {
41072
41516
  }
41073
41517
  ),
41074
41518
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `mt-1.5 sm:mt-2 flex items-center gap-2 text-xs text-gray-400 ${message.role === "user" ? "justify-end" : "justify-start"}`, children: [
41075
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime3(message.created_at) }),
41519
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime4(message.created_at) }),
41076
41520
  message.role === "assistant" && message.id !== -1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
41077
41521
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1 h-1 bg-gray-300 rounded-full" }),
41078
41522
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Axel" })