@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.mjs CHANGED
@@ -2896,8 +2896,8 @@ var AuthService = class {
2896
2896
  "Authorization": `Bearer ${accessToken}`,
2897
2897
  "Content-Type": "application/json"
2898
2898
  },
2899
- timeout: 3e5,
2900
- // 5 minutes
2899
+ timeout: 1e4,
2900
+ // 10 seconds
2901
2901
  retries: 1,
2902
2902
  silentErrors: false
2903
2903
  // We want to know about auth errors
@@ -2934,8 +2934,8 @@ var AuthService = class {
2934
2934
  "Authorization": `Bearer ${accessToken}`,
2935
2935
  "Content-Type": "application/json"
2936
2936
  },
2937
- timeout: 3e5,
2938
- // 5 minutes
2937
+ timeout: 1e4,
2938
+ // 10 seconds
2939
2939
  retries: 2,
2940
2940
  // More retries for validation
2941
2941
  silentErrors: true,
@@ -2967,8 +2967,8 @@ var AuthService = class {
2967
2967
  "Authorization": `Bearer ${accessToken}`,
2968
2968
  "Content-Type": "application/json"
2969
2969
  },
2970
- timeout: 3e5,
2971
- // 5 minutes
2970
+ timeout: 1e4,
2971
+ // 10 seconds
2972
2972
  retries: 1,
2973
2973
  silentErrors: false
2974
2974
  }
@@ -10883,7 +10883,7 @@ function useDateFormatter() {
10883
10883
  },
10884
10884
  [defaultTimezone, defaultLocale, dateFormatOptions]
10885
10885
  );
10886
- const formatTime3 = useCallback(
10886
+ const formatTime4 = useCallback(
10887
10887
  (date, formatString) => {
10888
10888
  const dateObj = typeof date === "string" ? parseISO(date) : date;
10889
10889
  if (!isValid(dateObj)) return "Invalid Time";
@@ -10914,7 +10914,7 @@ function useDateFormatter() {
10914
10914
  }, []);
10915
10915
  return {
10916
10916
  formatDate,
10917
- formatTime: formatTime3,
10917
+ formatTime: formatTime4,
10918
10918
  formatDateTime,
10919
10919
  getNow,
10920
10920
  timezone: defaultTimezone || "UTC",
@@ -23900,7 +23900,7 @@ var HourlyOutputChartComponent = ({
23900
23900
  endHour = Math.floor(endDecimalHour) % 24;
23901
23901
  endMinute = Math.round(endDecimalHour % 1 * 60);
23902
23902
  }
23903
- const formatTime3 = (h, m) => {
23903
+ const formatTime4 = (h, m) => {
23904
23904
  const period = h >= 12 ? "PM" : "AM";
23905
23905
  const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
23906
23906
  if (m === 0) {
@@ -23908,7 +23908,7 @@ var HourlyOutputChartComponent = ({
23908
23908
  }
23909
23909
  return `${hour12}:${m.toString().padStart(2, "0")}${period}`;
23910
23910
  };
23911
- return `${formatTime3(startHour, startMinute)}-${formatTime3(endHour, endMinute)}`;
23911
+ return `${formatTime4(startHour, startMinute)}-${formatTime4(endHour, endMinute)}`;
23912
23912
  }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
23913
23913
  const formatTimeRange = React23__default.useCallback((hourIndex) => {
23914
23914
  const isLastHour = hourIndex === SHIFT_DURATION - 1;
@@ -23924,12 +23924,12 @@ var HourlyOutputChartComponent = ({
23924
23924
  endHour = Math.floor(endDecimalHour) % 24;
23925
23925
  endMinute = Math.round(endDecimalHour % 1 * 60);
23926
23926
  }
23927
- const formatTime3 = (h, m) => {
23927
+ const formatTime4 = (h, m) => {
23928
23928
  const period = h >= 12 ? "PM" : "AM";
23929
23929
  const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
23930
23930
  return `${hour12}:${m.toString().padStart(2, "0")} ${period}`;
23931
23931
  };
23932
- return `${formatTime3(startHour, startMinute)} - ${formatTime3(endHour, endMinute)}`;
23932
+ return `${formatTime4(startHour, startMinute)} - ${formatTime4(endHour, endMinute)}`;
23933
23933
  }, [shiftStartTime.decimalHour, SHIFT_DURATION, shiftEndTime]);
23934
23934
  const chartData = React23__default.useMemo(() => {
23935
23935
  return Array.from({ length: SHIFT_DURATION }, (_, i) => {
@@ -25100,7 +25100,7 @@ var SOPComplianceChart = ({
25100
25100
  }
25101
25101
  };
25102
25102
  }, [data, animateToNewData, mockData]);
25103
- const formatTime3 = (minuteIndex) => {
25103
+ const formatTime4 = (minuteIndex) => {
25104
25104
  const totalMinutes = shiftStartHour * 60 + minuteIndex;
25105
25105
  const hours = Math.floor(totalMinutes / 60) % 24;
25106
25106
  const minutes = totalMinutes % 60;
@@ -25112,7 +25112,7 @@ var SOPComplianceChart = ({
25112
25112
  const hasDataForMinute = index < animatedData.length - 10;
25113
25113
  return {
25114
25114
  minute: index,
25115
- time: formatTime3(index),
25115
+ time: formatTime4(index),
25116
25116
  compliance: hasDataForMinute ? animatedData[index] : null
25117
25117
  };
25118
25118
  });
@@ -25546,7 +25546,7 @@ var DateTimeDisplay = ({
25546
25546
  const {
25547
25547
  defaultTimezone
25548
25548
  } = useDateTimeConfig();
25549
- const { formatDate, formatTime: formatTime3 } = useDateFormatter();
25549
+ const { formatDate, formatTime: formatTime4 } = useDateFormatter();
25550
25550
  const [now2, setNow] = useState(() => getCurrentTimeInZone(defaultTimezone || "UTC"));
25551
25551
  useEffect(() => {
25552
25552
  const timerId = setInterval(() => {
@@ -25558,7 +25558,7 @@ var DateTimeDisplay = ({
25558
25558
  return null;
25559
25559
  }
25560
25560
  const formattedDate = showDate ? formatDate(now2) : "";
25561
- const formattedTime = showTime ? formatTime3(now2) : "";
25561
+ const formattedTime = showTime ? formatTime4(now2) : "";
25562
25562
  return /* @__PURE__ */ jsxs("div", { className: clsx_default("flex items-center space-x-2 text-sm text-gray-700 dark:text-gray-300", className), children: [
25563
25563
  showDate && /* @__PURE__ */ jsx("span", { className: "date-display", "aria-label": `Current date: ${formattedDate}`, children: formattedDate }),
25564
25564
  showDate && showTime && formattedDate && formattedTime && /* @__PURE__ */ jsx("span", { className: "separator", "aria-hidden": "true", children: "|" }),
@@ -25722,7 +25722,7 @@ var BreakNotificationPopup = ({
25722
25722
  const handlePrevious = () => {
25723
25723
  setCurrentIndex((prev) => (prev - 1 + visibleBreaks.length) % visibleBreaks.length);
25724
25724
  };
25725
- const formatTime3 = (minutes) => {
25725
+ const formatTime4 = (minutes) => {
25726
25726
  const hours = Math.floor(minutes / 60);
25727
25727
  const mins = minutes % 60;
25728
25728
  if (hours > 0) {
@@ -25796,9 +25796,9 @@ var BreakNotificationPopup = ({
25796
25796
  formatTo12Hour(currentBreak.endTime)
25797
25797
  ] }),
25798
25798
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 mb-2", children: [
25799
- formatTime3(currentBreak.elapsedMinutes),
25799
+ formatTime4(currentBreak.elapsedMinutes),
25800
25800
  " elapsed of ",
25801
- formatTime3(currentBreak.duration),
25801
+ formatTime4(currentBreak.duration),
25802
25802
  " total"
25803
25803
  ] }),
25804
25804
  /* @__PURE__ */ jsx("div", { className: "w-full bg-gray-200 rounded-full h-1.5", children: /* @__PURE__ */ jsx(
@@ -26102,64 +26102,208 @@ var getSeverityColor = (severity) => {
26102
26102
  return "bg-gray-500";
26103
26103
  }
26104
26104
  };
26105
- var PlayPauseIndicator = ({
26106
- show,
26105
+ var formatTime2 = (seconds) => {
26106
+ if (!seconds || isNaN(seconds)) return "0:00";
26107
+ const h = Math.floor(seconds / 3600);
26108
+ const m = Math.floor(seconds % 3600 / 60);
26109
+ const s = Math.floor(seconds % 60);
26110
+ if (h > 0) {
26111
+ return `${h}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
26112
+ }
26113
+ return `${m}:${s.toString().padStart(2, "0")}`;
26114
+ };
26115
+ var VideoControls = ({
26107
26116
  isPlaying,
26108
- duration = 600
26117
+ currentTime,
26118
+ duration,
26119
+ buffered,
26120
+ showControls,
26121
+ controlsPinned = false,
26122
+ onTogglePinControls,
26123
+ playbackRate = 1,
26124
+ onPlayPause,
26125
+ onSeek,
26126
+ onSeekStart,
26127
+ onSeekEnd,
26128
+ onToggleFullscreen,
26129
+ onPlaybackRateChange,
26130
+ className = ""
26109
26131
  }) => {
26110
- const [isVisible, setIsVisible] = useState(false);
26111
- const [isFading, setIsFading] = useState(false);
26132
+ const [isDragging, setIsDragging] = useState(false);
26133
+ const [dragTime, setDragTime] = useState(0);
26134
+ const [isHoveringProgressBar, setIsHoveringProgressBar] = useState(false);
26135
+ const [showSpeedMenu, setShowSpeedMenu] = useState(false);
26136
+ const speedMenuRef = useRef(null);
26137
+ const progressColor = "#4b5563";
26138
+ const controlsVisible = showControls || controlsPinned;
26139
+ const getPercentage = (current, total) => {
26140
+ if (!total || total === 0) return 0;
26141
+ return Math.min(Math.max(current / total * 100, 0), 100);
26142
+ };
26143
+ const handleSeekChange = (e) => {
26144
+ const newTime = parseFloat(e.target.value);
26145
+ setDragTime(newTime);
26146
+ onSeek(newTime);
26147
+ };
26148
+ const handleSeekStart = () => {
26149
+ setIsDragging(true);
26150
+ setDragTime(currentTime);
26151
+ onSeekStart?.();
26152
+ };
26153
+ const handleSeekEnd = () => {
26154
+ setIsDragging(false);
26155
+ onSeekEnd?.();
26156
+ };
26112
26157
  useEffect(() => {
26113
- if (show) {
26114
- setIsVisible(true);
26115
- setIsFading(false);
26116
- const fadeTimer = setTimeout(() => {
26117
- setIsFading(true);
26118
- }, 100);
26119
- const hideTimer = setTimeout(() => {
26120
- setIsVisible(false);
26121
- setIsFading(false);
26122
- }, duration);
26123
- return () => {
26124
- clearTimeout(fadeTimer);
26125
- clearTimeout(hideTimer);
26126
- };
26127
- }
26128
- }, [show, duration]);
26129
- if (!isVisible) return null;
26130
- return /* @__PURE__ */ jsx(
26158
+ const handleClickOutside = (event) => {
26159
+ if (speedMenuRef.current && !speedMenuRef.current.contains(event.target)) {
26160
+ setShowSpeedMenu(false);
26161
+ }
26162
+ };
26163
+ document.addEventListener("mousedown", handleClickOutside);
26164
+ return () => {
26165
+ document.removeEventListener("mousedown", handleClickOutside);
26166
+ };
26167
+ }, []);
26168
+ const displayTime = isDragging ? dragTime : currentTime;
26169
+ const progressPercent = getPercentage(displayTime, duration);
26170
+ const bufferedPercent = getPercentage(buffered, duration);
26171
+ return /* @__PURE__ */ jsxs(
26131
26172
  "div",
26132
26173
  {
26133
- className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
26134
- style: {
26135
- opacity: isFading ? 0 : 1,
26136
- transition: `opacity ${duration - 100}ms ease-out`
26137
- },
26138
- children: /* @__PURE__ */ jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
26139
- // Play icon (triangle)
26140
- /* @__PURE__ */ jsx(
26141
- "svg",
26142
- {
26143
- xmlns: "http://www.w3.org/2000/svg",
26144
- viewBox: "0 0 24 24",
26145
- fill: "white",
26146
- className: "w-16 h-16",
26147
- children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" })
26148
- }
26149
- )
26150
- ) : (
26151
- // Pause icon (two bars)
26152
- /* @__PURE__ */ jsx(
26153
- "svg",
26174
+ 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}`,
26175
+ style: { touchAction: "none" },
26176
+ children: [
26177
+ /* @__PURE__ */ jsxs(
26178
+ "div",
26154
26179
  {
26155
- xmlns: "http://www.w3.org/2000/svg",
26156
- viewBox: "0 0 24 24",
26157
- fill: "white",
26158
- className: "w-16 h-16",
26159
- children: /* @__PURE__ */ jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
26180
+ className: "relative h-1 mb-4 group cursor-pointer",
26181
+ onMouseEnter: () => setIsHoveringProgressBar(true),
26182
+ onMouseLeave: () => setIsHoveringProgressBar(false),
26183
+ children: [
26184
+ /* @__PURE__ */ jsx("div", { className: "absolute -top-2 -bottom-2 left-0 right-0 z-20" }),
26185
+ /* @__PURE__ */ jsx("div", { className: "absolute top-0 left-0 right-0 bottom-0 bg-white/20 rounded-full overflow-hidden z-0", children: /* @__PURE__ */ jsx(
26186
+ "div",
26187
+ {
26188
+ className: "absolute top-0 left-0 bottom-0 bg-white/40 transition-all duration-200",
26189
+ style: { width: `${bufferedPercent}%` }
26190
+ }
26191
+ ) }),
26192
+ /* @__PURE__ */ jsx(
26193
+ "div",
26194
+ {
26195
+ className: "absolute top-0 left-0 bottom-0 bg-[#007bff] transition-all duration-75 z-10",
26196
+ style: { width: `${progressPercent}%`, backgroundColor: progressColor },
26197
+ children: /* @__PURE__ */ jsx(
26198
+ "div",
26199
+ {
26200
+ 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"}`,
26201
+ style: { backgroundColor: progressColor }
26202
+ }
26203
+ )
26204
+ }
26205
+ ),
26206
+ /* @__PURE__ */ jsx(
26207
+ "input",
26208
+ {
26209
+ type: "range",
26210
+ min: "0",
26211
+ max: duration || 100,
26212
+ step: "0.1",
26213
+ value: displayTime,
26214
+ onChange: handleSeekChange,
26215
+ onMouseDown: handleSeekStart,
26216
+ onMouseUp: handleSeekEnd,
26217
+ onTouchStart: handleSeekStart,
26218
+ onTouchEnd: handleSeekEnd,
26219
+ className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer z-30 margin-0 padding-0"
26220
+ }
26221
+ )
26222
+ ]
26160
26223
  }
26161
- )
26162
- ) })
26224
+ ),
26225
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-white", children: [
26226
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
26227
+ /* @__PURE__ */ jsx(
26228
+ "button",
26229
+ {
26230
+ onClick: (e) => {
26231
+ e.stopPropagation();
26232
+ onPlayPause();
26233
+ },
26234
+ className: "hover:text-[#007bff] transition-colors focus:outline-none",
26235
+ "aria-label": isPlaying ? "Pause" : "Play",
26236
+ children: isPlaying ? /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" }) }) : /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" }) })
26237
+ }
26238
+ ),
26239
+ /* @__PURE__ */ jsxs("div", { className: "text-xs font-medium font-sans", children: [
26240
+ /* @__PURE__ */ jsx("span", { children: formatTime2(displayTime) }),
26241
+ /* @__PURE__ */ jsx("span", { className: "mx-1 text-white/70", children: "/" }),
26242
+ /* @__PURE__ */ jsx("span", { className: "text-white/70", children: formatTime2(duration) })
26243
+ ] })
26244
+ ] }),
26245
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
26246
+ onTogglePinControls && /* @__PURE__ */ jsx(
26247
+ "button",
26248
+ {
26249
+ onClick: (e) => {
26250
+ e.stopPropagation();
26251
+ onTogglePinControls();
26252
+ },
26253
+ className: `transition-colors focus:outline-none ${controlsPinned ? "text-[#007bff]" : "hover:text-[#007bff]"}`,
26254
+ "aria-label": controlsPinned ? "Unpin controls" : "Pin controls",
26255
+ title: controlsPinned ? "Unpin controls" : "Pin controls",
26256
+ children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: controlsPinned ? /* @__PURE__ */ jsx("path", { d: "M9 3h6l-1 7h3v2h-4.5l-.5 4.5-2 1L10 12H6v-2h3z" }) : /* @__PURE__ */ jsx("path", { d: "M9 3h6l-1 7h3v2h-4v5l-2 1-1-6H6v-2h3z" }) })
26257
+ }
26258
+ ),
26259
+ onPlaybackRateChange && /* @__PURE__ */ jsxs("div", { className: "relative", ref: speedMenuRef, children: [
26260
+ /* @__PURE__ */ jsxs(
26261
+ "button",
26262
+ {
26263
+ onClick: (e) => {
26264
+ e.stopPropagation();
26265
+ setShowSpeedMenu(!showSpeedMenu);
26266
+ },
26267
+ className: "text-xs font-medium hover:text-[#007bff] transition-colors focus:outline-none min-w-[32px]",
26268
+ "aria-label": "Playback Speed",
26269
+ children: [
26270
+ playbackRate,
26271
+ "x"
26272
+ ]
26273
+ }
26274
+ ),
26275
+ showSpeedMenu && /* @__PURE__ */ 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__ */ jsxs(
26276
+ "button",
26277
+ {
26278
+ onClick: (e) => {
26279
+ e.stopPropagation();
26280
+ onPlaybackRateChange(rate);
26281
+ setShowSpeedMenu(false);
26282
+ },
26283
+ className: `block w-full text-left px-4 py-2 text-xs hover:bg-white/20 transition-colors ${playbackRate === rate ? "text-[#007bff] font-bold" : ""}`,
26284
+ children: [
26285
+ rate,
26286
+ "x"
26287
+ ]
26288
+ },
26289
+ rate
26290
+ )) })
26291
+ ] }),
26292
+ onToggleFullscreen && /* @__PURE__ */ jsx(
26293
+ "button",
26294
+ {
26295
+ onClick: (e) => {
26296
+ e.stopPropagation();
26297
+ onToggleFullscreen();
26298
+ },
26299
+ className: "hover:text-[#007bff] transition-colors focus:outline-none",
26300
+ "aria-label": "Toggle Fullscreen",
26301
+ children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
26302
+ }
26303
+ )
26304
+ ] })
26305
+ ] })
26306
+ ]
26163
26307
  }
26164
26308
  );
26165
26309
  };
@@ -26287,9 +26431,15 @@ var HlsVideoPlayer = forwardRef(({
26287
26431
  const blobUrlRef = useRef(null);
26288
26432
  const [isReady, setIsReady] = useState(false);
26289
26433
  const [isLoading, setIsLoading] = useState(true);
26290
- const [showIndicator, setShowIndicator] = useState(false);
26291
- const [indicatorIsPlaying, setIndicatorIsPlaying] = useState(false);
26292
- const indicatorKeyRef = useRef(0);
26434
+ const [showControls, setShowControls] = useState(true);
26435
+ const [controlsPinned, setControlsPinned] = useState(false);
26436
+ const [isPlaying, setIsPlaying] = useState(false);
26437
+ const [currentTime, setCurrentTime] = useState(0);
26438
+ const [duration, setDuration] = useState(0);
26439
+ const [buffered, setBuffered] = useState(0);
26440
+ const [playbackRate, setPlaybackRate] = useState(1);
26441
+ const userSeekingRef = useRef(false);
26442
+ const controlsTimeoutRef = useRef(null);
26293
26443
  const eventCallbacksRef = useRef({
26294
26444
  onReady,
26295
26445
  onPlay,
@@ -26468,8 +26618,6 @@ var HlsVideoPlayer = forwardRef(({
26468
26618
  }
26469
26619
  });
26470
26620
  hls.on(Events.FRAG_LOADING, () => {
26471
- setIsLoading(true);
26472
- eventCallbacksRef.current.onLoadingChange?.(true);
26473
26621
  });
26474
26622
  hls.on(Events.FRAG_LOADED, () => {
26475
26623
  setIsLoading(false);
@@ -26523,25 +26671,52 @@ var HlsVideoPlayer = forwardRef(({
26523
26671
  const handleCanPlay = () => {
26524
26672
  if (!hlsRef.current) {
26525
26673
  setIsReady(true);
26526
- onReady?.(player);
26527
26674
  }
26675
+ setIsLoading(false);
26676
+ eventCallbacksRef.current.onLoadingChange?.(false);
26677
+ onReady?.(player);
26678
+ };
26679
+ const handlePlay = () => {
26680
+ setIsPlaying(true);
26681
+ eventCallbacksRef.current.onPlay?.(player);
26682
+ };
26683
+ const handlePause = () => {
26684
+ if (userSeekingRef.current && videoRef.current) {
26685
+ videoRef.current.play().catch((err) => console.warn("Auto-resume after seek pause failed:", err));
26686
+ return;
26687
+ }
26688
+ setIsPlaying(false);
26689
+ eventCallbacksRef.current.onPause?.(player);
26528
26690
  };
26529
- const handlePlay = () => eventCallbacksRef.current.onPlay?.(player);
26530
- const handlePause = () => eventCallbacksRef.current.onPause?.(player);
26531
26691
  const handlePlaying = () => {
26532
26692
  setIsLoading(false);
26693
+ setIsPlaying(true);
26533
26694
  eventCallbacksRef.current.onLoadingChange?.(false);
26534
26695
  eventCallbacksRef.current.onPlaying?.(player);
26535
26696
  };
26536
26697
  const handleTimeUpdate = () => {
26537
26698
  const currentTime2 = video.currentTime || 0;
26699
+ setCurrentTime(currentTime2);
26700
+ if (video.buffered.length > 0) {
26701
+ for (let i = 0; i < video.buffered.length; i++) {
26702
+ if (video.buffered.start(i) <= currentTime2 && video.buffered.end(i) >= currentTime2) {
26703
+ setBuffered(video.buffered.end(i));
26704
+ break;
26705
+ }
26706
+ }
26707
+ }
26538
26708
  eventCallbacksRef.current.onTimeUpdate?.(player, currentTime2);
26539
26709
  };
26540
26710
  const handleDurationChange = () => {
26541
26711
  const duration2 = video.duration || 0;
26712
+ setDuration(duration2);
26542
26713
  eventCallbacksRef.current.onDurationChange?.(player, duration2);
26543
26714
  };
26544
- const handleEnded = () => eventCallbacksRef.current.onEnded?.(player);
26715
+ const handleEnded = () => {
26716
+ setIsPlaying(false);
26717
+ userSeekingRef.current = false;
26718
+ eventCallbacksRef.current.onEnded?.(player);
26719
+ };
26545
26720
  const handleLoadStart = () => {
26546
26721
  setIsLoading(true);
26547
26722
  eventCallbacksRef.current.onLoadingChange?.(true);
@@ -26557,8 +26732,19 @@ var HlsVideoPlayer = forwardRef(({
26557
26732
  setIsLoading(true);
26558
26733
  eventCallbacksRef.current.onLoadingChange?.(true);
26559
26734
  };
26560
- const handleSeeking = () => eventCallbacksRef.current.onSeeking?.(player);
26561
- const handleSeeked = () => eventCallbacksRef.current.onSeeked?.(player);
26735
+ const handleSeeking = () => {
26736
+ userSeekingRef.current = true;
26737
+ eventCallbacksRef.current.onSeeking?.(player);
26738
+ };
26739
+ const handleSeeked = () => {
26740
+ setIsLoading(false);
26741
+ eventCallbacksRef.current.onLoadingChange?.(false);
26742
+ if (videoRef.current) {
26743
+ videoRef.current.play().catch((err) => console.warn("Resume playback after seek failed:", err));
26744
+ }
26745
+ userSeekingRef.current = false;
26746
+ eventCallbacksRef.current.onSeeked?.(player);
26747
+ };
26562
26748
  const handleError = () => {
26563
26749
  const error = video.error;
26564
26750
  if (error) {
@@ -26571,6 +26757,10 @@ var HlsVideoPlayer = forwardRef(({
26571
26757
  eventCallbacksRef.current.onError?.(player, errorInfo);
26572
26758
  }
26573
26759
  };
26760
+ const handlePlaybackRateChange2 = (e) => {
26761
+ const target = e.target;
26762
+ setPlaybackRate(target.playbackRate);
26763
+ };
26574
26764
  video.addEventListener("canplay", handleCanPlay);
26575
26765
  video.addEventListener("play", handlePlay);
26576
26766
  video.addEventListener("pause", handlePause);
@@ -26585,6 +26775,7 @@ var HlsVideoPlayer = forwardRef(({
26585
26775
  video.addEventListener("seeking", handleSeeking);
26586
26776
  video.addEventListener("seeked", handleSeeked);
26587
26777
  video.addEventListener("error", handleError);
26778
+ video.addEventListener("ratechange", handlePlaybackRateChange2);
26588
26779
  return () => {
26589
26780
  video.removeEventListener("canplay", handleCanPlay);
26590
26781
  video.removeEventListener("play", handlePlay);
@@ -26600,6 +26791,7 @@ var HlsVideoPlayer = forwardRef(({
26600
26791
  video.removeEventListener("seeking", handleSeeking);
26601
26792
  video.removeEventListener("seeked", handleSeeked);
26602
26793
  video.removeEventListener("error", handleError);
26794
+ video.removeEventListener("ratechange", handlePlaybackRateChange2);
26603
26795
  };
26604
26796
  }, [
26605
26797
  src,
@@ -26628,20 +26820,46 @@ var HlsVideoPlayer = forwardRef(({
26628
26820
  }
26629
26821
  }
26630
26822
  }, [autoplay]);
26823
+ const resetControlsTimeout = useCallback(() => {
26824
+ if (controlsPinned) {
26825
+ setShowControls(true);
26826
+ return;
26827
+ }
26828
+ setShowControls(true);
26829
+ if (controlsTimeoutRef.current) {
26830
+ clearTimeout(controlsTimeoutRef.current);
26831
+ }
26832
+ if (isPlaying) {
26833
+ controlsTimeoutRef.current = setTimeout(() => {
26834
+ setShowControls(false);
26835
+ }, 3e3);
26836
+ }
26837
+ }, [isPlaying, controlsPinned]);
26838
+ const handleMouseMove = useCallback(() => {
26839
+ resetControlsTimeout();
26840
+ }, [resetControlsTimeout]);
26841
+ useEffect(() => {
26842
+ resetControlsTimeout();
26843
+ return () => {
26844
+ if (controlsTimeoutRef.current) {
26845
+ clearTimeout(controlsTimeoutRef.current);
26846
+ }
26847
+ };
26848
+ }, [isPlaying, resetControlsTimeout]);
26631
26849
  const play = useCallback(() => {
26632
26850
  return videoRef.current?.play();
26633
26851
  }, []);
26634
26852
  const pause = useCallback(() => {
26635
26853
  videoRef.current?.pause();
26636
26854
  }, []);
26637
- const currentTime = useCallback((time2) => {
26855
+ const currentTimeProp = useCallback((time2) => {
26638
26856
  if (time2 !== void 0 && videoRef.current) {
26639
26857
  videoRef.current.currentTime = time2;
26640
26858
  return time2;
26641
26859
  }
26642
26860
  return videoRef.current?.currentTime || 0;
26643
26861
  }, []);
26644
- const duration = useCallback(() => {
26862
+ const durationProp = useCallback(() => {
26645
26863
  return videoRef.current?.duration || 0;
26646
26864
  }, []);
26647
26865
  const paused = useCallback(() => {
@@ -26654,95 +26872,144 @@ var HlsVideoPlayer = forwardRef(({
26654
26872
  }
26655
26873
  return videoRef.current?.muted ?? false;
26656
26874
  }, []);
26657
- const volume = useCallback((level) => {
26875
+ const volumeProp = useCallback((level) => {
26658
26876
  if (level !== void 0 && videoRef.current) {
26659
26877
  videoRef.current.volume = level;
26660
26878
  return level;
26661
26879
  }
26662
26880
  return videoRef.current?.volume ?? 1;
26663
26881
  }, []);
26664
- const playbackRate = useCallback((rate) => {
26882
+ const playbackRateProp = useCallback((rate) => {
26665
26883
  if (rate !== void 0 && videoRef.current) {
26666
26884
  videoRef.current.playbackRate = rate;
26667
26885
  return rate;
26668
26886
  }
26669
26887
  return videoRef.current?.playbackRate ?? 1;
26670
26888
  }, []);
26889
+ const handleTogglePlay = useCallback(() => {
26890
+ if (videoRef.current) {
26891
+ if (videoRef.current.paused) {
26892
+ videoRef.current.play();
26893
+ } else {
26894
+ videoRef.current.pause();
26895
+ }
26896
+ }
26897
+ }, []);
26898
+ const handleSeek = useCallback((time2) => {
26899
+ if (videoRef.current) {
26900
+ videoRef.current.currentTime = time2;
26901
+ videoRef.current.play().catch((err) => console.warn("Resume playback failed during seek:", err));
26902
+ }
26903
+ }, []);
26904
+ const handleSeekStart = useCallback(() => {
26905
+ userSeekingRef.current = true;
26906
+ }, []);
26907
+ const handleSeekEnd = useCallback(() => {
26908
+ if (videoRef.current) {
26909
+ videoRef.current.play().catch((err) => console.warn("Resume playback failed after seek:", err));
26910
+ }
26911
+ }, []);
26912
+ const handlePlaybackRateChange = useCallback((rate) => {
26913
+ if (videoRef.current) {
26914
+ videoRef.current.playbackRate = rate;
26915
+ }
26916
+ }, []);
26917
+ const handleToggleFullscreen = useCallback(() => {
26918
+ if (videoContainerRef.current) {
26919
+ if (!document.fullscreenElement) {
26920
+ videoContainerRef.current.requestFullscreen();
26921
+ } else {
26922
+ document.exitFullscreen();
26923
+ }
26924
+ }
26925
+ }, []);
26671
26926
  useImperativeHandle(ref, () => ({
26672
26927
  hls: hlsRef.current,
26673
26928
  video: videoRef.current,
26674
26929
  play,
26675
26930
  pause,
26676
- currentTime,
26677
- duration,
26931
+ currentTime: currentTimeProp,
26932
+ duration: durationProp,
26678
26933
  paused,
26679
26934
  mute,
26680
- volume,
26681
- playbackRate,
26935
+ volume: volumeProp,
26936
+ playbackRate: playbackRateProp,
26682
26937
  dispose,
26683
26938
  isReady,
26684
26939
  // For backward compatibility with Video.js API
26685
26940
  player: playerLikeObject()
26686
- }), [play, pause, currentTime, duration, paused, mute, volume, playbackRate, dispose, isReady, playerLikeObject]);
26687
- const handleClickWithIndicator = useCallback(() => {
26688
- if (!onClick || !videoRef.current) return;
26689
- const willBePlaying = videoRef.current.paused;
26690
- setIndicatorIsPlaying(willBePlaying);
26691
- setShowIndicator(false);
26692
- setTimeout(() => {
26693
- indicatorKeyRef.current += 1;
26694
- setShowIndicator(true);
26695
- }, 0);
26696
- onClick();
26697
- }, [onClick]);
26698
- return /* @__PURE__ */ jsxs("div", { className: `hls-video-player-wrapper ${className}`, style: { position: "relative", width: "100%", height: "100%" }, children: [
26699
- /* @__PURE__ */ jsx(
26700
- "div",
26701
- {
26702
- className: "hls-video-player-container",
26703
- ref: videoContainerRef,
26704
- children: /* @__PURE__ */ jsx(
26705
- "video",
26941
+ }), [play, pause, currentTimeProp, durationProp, paused, mute, volumeProp, playbackRateProp, dispose, isReady, playerLikeObject]);
26942
+ const handleContainerClick = useCallback(() => {
26943
+ if (!onClick && !controls) {
26944
+ handleTogglePlay();
26945
+ }
26946
+ if (onClick) {
26947
+ onClick();
26948
+ }
26949
+ }, [onClick, controls, handleTogglePlay]);
26950
+ return /* @__PURE__ */ jsxs(
26951
+ "div",
26952
+ {
26953
+ className: `hls-video-player-wrapper ${className} group`,
26954
+ style: { position: "relative", width: "100%", height: "100%" },
26955
+ onMouseMove: handleMouseMove,
26956
+ onMouseLeave: () => isPlaying && !controlsPinned && setShowControls(false),
26957
+ children: [
26958
+ /* @__PURE__ */ jsxs(
26959
+ "div",
26706
26960
  {
26707
- ref: videoRef,
26708
- className: "hls-video-element",
26709
- poster,
26710
- controls,
26711
- loop,
26712
- muted,
26713
- playsInline,
26714
- autoPlay: autoplay,
26715
- preload: "metadata"
26961
+ className: "hls-video-player-container",
26962
+ ref: videoContainerRef,
26963
+ onClick: handleContainerClick,
26964
+ children: [
26965
+ /* @__PURE__ */ jsx(
26966
+ "video",
26967
+ {
26968
+ ref: videoRef,
26969
+ className: "hls-video-element",
26970
+ poster,
26971
+ controls: false,
26972
+ loop,
26973
+ muted,
26974
+ playsInline,
26975
+ autoPlay: autoplay,
26976
+ preload: "metadata"
26977
+ }
26978
+ ),
26979
+ controls && /* @__PURE__ */ jsx(
26980
+ VideoControls,
26981
+ {
26982
+ isPlaying,
26983
+ currentTime,
26984
+ duration,
26985
+ buffered,
26986
+ showControls: controlsPinned || showControls || !isPlaying,
26987
+ controlsPinned,
26988
+ playbackRate,
26989
+ onPlayPause: handleTogglePlay,
26990
+ onSeek: handleSeek,
26991
+ onSeekStart: handleSeekStart,
26992
+ onSeekEnd: handleSeekEnd,
26993
+ onPlaybackRateChange: handlePlaybackRateChange,
26994
+ onTogglePinControls: () => setControlsPinned((prev) => {
26995
+ const next = !prev;
26996
+ if (next) {
26997
+ setShowControls(true);
26998
+ } else {
26999
+ resetControlsTimeout();
27000
+ }
27001
+ return next;
27002
+ }),
27003
+ onToggleFullscreen: handleToggleFullscreen
27004
+ }
27005
+ )
27006
+ ]
26716
27007
  }
26717
- )
26718
- }
26719
- ),
26720
- isLoading && !externalLoadingControl && /* @__PURE__ */ jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
26721
- onClick && !controls && /* @__PURE__ */ jsx(
26722
- "div",
26723
- {
26724
- onClick: handleClickWithIndicator,
26725
- style: {
26726
- position: "absolute",
26727
- top: 0,
26728
- left: 0,
26729
- right: 0,
26730
- bottom: 0,
26731
- zIndex: 1,
26732
- cursor: "pointer"
26733
- },
26734
- "aria-label": "Click to play/pause"
26735
- }
26736
- ),
26737
- onClick && !controls && /* @__PURE__ */ jsx(
26738
- PlayPauseIndicator,
26739
- {
26740
- show: showIndicator,
26741
- isPlaying: indicatorIsPlaying
26742
- },
26743
- indicatorKeyRef.current
26744
- )
26745
- ] });
27008
+ ),
27009
+ isLoading && !externalLoadingControl && /* @__PURE__ */ jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) })
27010
+ ]
27011
+ }
27012
+ );
26746
27013
  });
26747
27014
  HlsVideoPlayer.displayName = "HlsVideoPlayer";
26748
27015
  var VideoPlayer = HlsVideoPlayer;
@@ -26750,6 +27017,7 @@ var CroppedHlsVideoPlayer = forwardRef(({
26750
27017
  crop,
26751
27018
  debug = false,
26752
27019
  onClick,
27020
+ controls = true,
26753
27021
  ...videoProps
26754
27022
  }, ref) => {
26755
27023
  const {
@@ -26771,9 +27039,15 @@ var CroppedHlsVideoPlayer = forwardRef(({
26771
27039
  const [isVideoReady, setIsVideoReady] = useState(false);
26772
27040
  const [canvasDimensions, setCanvasDimensions] = useState({ width: 0, height: 0 });
26773
27041
  const [isProcessing, setIsProcessing] = useState(false);
26774
- const [showIndicator, setShowIndicator] = useState(false);
26775
- const [indicatorIsPlaying, setIndicatorIsPlaying] = useState(false);
26776
- const indicatorKeyRef = useRef(0);
27042
+ const [showControls, setShowControls] = useState(true);
27043
+ const [isPlaying, setIsPlaying] = useState(false);
27044
+ const [currentTime, setCurrentTime] = useState(0);
27045
+ const [duration, setDuration] = useState(0);
27046
+ const [buffered, setBuffered] = useState(0);
27047
+ const [playbackRate, setPlaybackRate] = useState(1);
27048
+ const controlsTimeoutRef = useRef(null);
27049
+ const userSeekingRef = useRef(false);
27050
+ const [controlsPinned, setControlsPinned] = useState(false);
26777
27051
  const stopCanvasRendering = useCallback(() => {
26778
27052
  if (animationFrameRef.current) {
26779
27053
  cancelAnimationFrame(animationFrameRef.current);
@@ -26858,7 +27132,7 @@ var CroppedHlsVideoPlayer = forwardRef(({
26858
27132
  const canvas = canvasRef.current;
26859
27133
  const video = videoElementRef.current;
26860
27134
  const ctx = canvas.getContext("2d");
26861
- if (!ctx || video.paused || video.ended) {
27135
+ if (!ctx || video.readyState < 2) {
26862
27136
  return;
26863
27137
  }
26864
27138
  const videoWidth = video.videoWidth;
@@ -26885,7 +27159,9 @@ var CroppedHlsVideoPlayer = forwardRef(({
26885
27159
  canvas.height
26886
27160
  // Destination (full canvas)
26887
27161
  );
26888
- animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
27162
+ if (!video.paused && !video.ended) {
27163
+ animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
27164
+ }
26889
27165
  }, [crop]);
26890
27166
  const handleVideoReady = useCallback((player) => {
26891
27167
  console.log("[CroppedHlsVideoPlayer] Video player ready");
@@ -26893,11 +27169,15 @@ var CroppedHlsVideoPlayer = forwardRef(({
26893
27169
  if (videoEl) {
26894
27170
  videoElementRef.current = videoEl;
26895
27171
  setIsVideoReady(true);
27172
+ if (videoEl.readyState >= 2) {
27173
+ renderFrameToCanvas();
27174
+ }
26896
27175
  }
26897
27176
  onReadyProp?.(player);
26898
- }, [onReadyProp]);
27177
+ }, [onReadyProp, renderFrameToCanvas]);
26899
27178
  const handleVideoPlay = useCallback((player) => {
26900
27179
  console.log("[CroppedHlsVideoPlayer] Video playing, starting canvas rendering");
27180
+ setIsPlaying(true);
26901
27181
  if (crop && canvasRef.current) {
26902
27182
  setIsProcessing(true);
26903
27183
  renderFrameToCanvas();
@@ -26905,43 +27185,40 @@ var CroppedHlsVideoPlayer = forwardRef(({
26905
27185
  onPlayProp?.(player);
26906
27186
  }, [crop, renderFrameToCanvas, onPlayProp]);
26907
27187
  const handleVideoPause = useCallback((player) => {
26908
- console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering and CLEARING canvas");
27188
+ console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering (keeping last frame)");
27189
+ if (userSeekingRef.current && hiddenVideoRef.current) {
27190
+ hiddenVideoRef.current.play()?.catch(() => {
27191
+ });
27192
+ return;
27193
+ }
26909
27194
  stopCanvasRendering();
26910
27195
  setIsProcessing(false);
26911
- if (canvasRef.current) {
26912
- const ctx = canvasRef.current.getContext("2d");
26913
- if (ctx) {
26914
- ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26915
- ctx.fillStyle = "black";
26916
- ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26917
- }
26918
- }
27196
+ setIsPlaying(false);
27197
+ renderFrameToCanvas();
26919
27198
  onPauseProp?.(player);
26920
- }, [stopCanvasRendering, onPauseProp]);
27199
+ }, [stopCanvasRendering, onPauseProp, renderFrameToCanvas]);
26921
27200
  const handleVideoEnded = useCallback((player) => {
26922
- console.log("[CroppedHlsVideoPlayer] Video ended, CLEARING canvas");
27201
+ console.log("[CroppedHlsVideoPlayer] Video ended, stopping canvas rendering (keeping last frame)");
26923
27202
  stopCanvasRendering();
26924
27203
  setIsProcessing(false);
26925
- if (canvasRef.current) {
26926
- const ctx = canvasRef.current.getContext("2d");
26927
- if (ctx) {
26928
- ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26929
- ctx.fillStyle = "black";
26930
- ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
26931
- }
26932
- }
27204
+ setIsPlaying(false);
27205
+ userSeekingRef.current = false;
26933
27206
  onEndedProp?.(player);
26934
27207
  }, [stopCanvasRendering, onEndedProp]);
26935
27208
  const handleSeeking = useCallback((player) => {
26936
27209
  console.log("[CroppedHlsVideoPlayer] Video seeking");
26937
- if (crop && !videoElementRef.current?.paused) {
27210
+ userSeekingRef.current = true;
27211
+ if (crop) {
26938
27212
  renderFrameToCanvas();
26939
27213
  }
26940
27214
  onSeekingProp?.(player);
26941
27215
  }, [crop, renderFrameToCanvas, onSeekingProp]);
26942
27216
  const handleSeeked = useCallback((player) => {
26943
27217
  console.log("[CroppedHlsVideoPlayer] Video seeked");
26944
- if (crop && !videoElementRef.current?.paused) {
27218
+ hiddenVideoRef.current?.play()?.catch(() => {
27219
+ });
27220
+ userSeekingRef.current = false;
27221
+ if (crop) {
26945
27222
  renderFrameToCanvas();
26946
27223
  }
26947
27224
  onSeekedProp?.(player);
@@ -26949,8 +27226,29 @@ var CroppedHlsVideoPlayer = forwardRef(({
26949
27226
  const handleLoadedMetadata = useCallback((player) => {
26950
27227
  console.log("[CroppedHlsVideoPlayer] Video metadata loaded");
26951
27228
  calculateCanvasDimensions();
27229
+ if (hiddenVideoRef.current?.video) {
27230
+ setDuration(hiddenVideoRef.current.video.duration || 0);
27231
+ }
27232
+ requestAnimationFrame(() => renderFrameToCanvas());
26952
27233
  onLoadedMetadataProp?.(player);
26953
- }, [calculateCanvasDimensions, onLoadedMetadataProp]);
27234
+ }, [calculateCanvasDimensions, onLoadedMetadataProp, renderFrameToCanvas]);
27235
+ const handleTimeUpdate = useCallback((player, time2) => {
27236
+ setCurrentTime(time2);
27237
+ if (hiddenVideoRef.current?.video && hiddenVideoRef.current.video.buffered.length > 0) {
27238
+ const video = hiddenVideoRef.current.video;
27239
+ for (let i = 0; i < video.buffered.length; i++) {
27240
+ if (video.buffered.start(i) <= time2 && video.buffered.end(i) >= time2) {
27241
+ setBuffered(video.buffered.end(i));
27242
+ break;
27243
+ }
27244
+ }
27245
+ }
27246
+ videoProps.onTimeUpdate?.(player, time2);
27247
+ }, [videoProps.onTimeUpdate]);
27248
+ const handleDurationChange = useCallback((player, dur) => {
27249
+ setDuration(dur);
27250
+ videoProps.onDurationChange?.(player, dur);
27251
+ }, [videoProps.onDurationChange]);
26954
27252
  useEffect(() => {
26955
27253
  calculateCanvasDimensions();
26956
27254
  const handleResize = () => {
@@ -26978,33 +27276,97 @@ var CroppedHlsVideoPlayer = forwardRef(({
26978
27276
  stopCanvasRendering();
26979
27277
  };
26980
27278
  }, [stopCanvasRendering]);
27279
+ const resetControlsTimeout = useCallback(() => {
27280
+ if (controlsPinned) {
27281
+ setShowControls(true);
27282
+ return;
27283
+ }
27284
+ setShowControls(true);
27285
+ if (controlsTimeoutRef.current) {
27286
+ clearTimeout(controlsTimeoutRef.current);
27287
+ }
27288
+ if (isPlaying) {
27289
+ controlsTimeoutRef.current = setTimeout(() => {
27290
+ setShowControls(false);
27291
+ }, 3e3);
27292
+ }
27293
+ }, [isPlaying, controlsPinned]);
27294
+ const handleMouseMove = useCallback(() => {
27295
+ resetControlsTimeout();
27296
+ }, [resetControlsTimeout]);
27297
+ useEffect(() => {
27298
+ resetControlsTimeout();
27299
+ return () => {
27300
+ if (controlsTimeoutRef.current) {
27301
+ clearTimeout(controlsTimeoutRef.current);
27302
+ }
27303
+ };
27304
+ }, [isPlaying, resetControlsTimeout]);
27305
+ const handleTogglePlay = useCallback(() => {
27306
+ if (hiddenVideoRef.current?.video) {
27307
+ if (hiddenVideoRef.current.video.paused) {
27308
+ hiddenVideoRef.current.play();
27309
+ } else {
27310
+ hiddenVideoRef.current.pause();
27311
+ }
27312
+ }
27313
+ }, []);
27314
+ const handleSeek = useCallback((time2) => {
27315
+ if (hiddenVideoRef.current) {
27316
+ hiddenVideoRef.current.currentTime(time2);
27317
+ hiddenVideoRef.current.play()?.catch(() => {
27318
+ });
27319
+ setTimeout(() => renderFrameToCanvas(), 50);
27320
+ }
27321
+ }, [renderFrameToCanvas]);
27322
+ const handleSeekStart = useCallback(() => {
27323
+ userSeekingRef.current = true;
27324
+ }, []);
27325
+ const handleSeekEnd = useCallback(() => {
27326
+ if (hiddenVideoRef.current) {
27327
+ hiddenVideoRef.current.play()?.catch(() => {
27328
+ });
27329
+ }
27330
+ }, []);
27331
+ const handlePlaybackRateChange = useCallback((rate) => {
27332
+ if (hiddenVideoRef.current) {
27333
+ hiddenVideoRef.current.playbackRate(rate);
27334
+ setPlaybackRate(rate);
27335
+ }
27336
+ }, []);
27337
+ const handleToggleFullscreen = useCallback(() => {
27338
+ if (videoContainerRef.current) {
27339
+ if (!document.fullscreenElement) {
27340
+ videoContainerRef.current.requestFullscreen();
27341
+ } else {
27342
+ document.exitFullscreen();
27343
+ }
27344
+ }
27345
+ }, []);
26981
27346
  if (!crop) {
26982
- return /* @__PURE__ */ jsx(HlsVideoPlayer, { ref, ...videoProps, onClick });
26983
- }
26984
- const handleClickWithIndicator = () => {
26985
- if (!onClick || !hiddenVideoRef.current?.video) return;
26986
- const video = hiddenVideoRef.current.video;
26987
- const willBePlaying = video.paused;
26988
- setIndicatorIsPlaying(willBePlaying);
26989
- setShowIndicator(false);
26990
- setTimeout(() => {
26991
- indicatorKeyRef.current += 1;
26992
- setShowIndicator(true);
26993
- }, 0);
26994
- onClick();
27347
+ return /* @__PURE__ */ jsx(HlsVideoPlayer, { ref, ...videoProps, onClick, controls });
27348
+ }
27349
+ const handleClick = () => {
27350
+ if (!onClick && !controls) {
27351
+ handleTogglePlay();
27352
+ }
27353
+ if (onClick) onClick();
26995
27354
  };
26996
27355
  return /* @__PURE__ */ jsxs(
26997
27356
  "div",
26998
27357
  {
26999
27358
  ref: videoContainerRef,
27000
- className: `relative w-full h-full flex items-center justify-center bg-black ${onClick ? "cursor-pointer" : ""} ${inheritedClassName}`,
27001
- onClick: handleClickWithIndicator,
27359
+ className: `relative w-full h-full flex items-center justify-center bg-black group ${inheritedClassName} ${onClick || controls ? "cursor-pointer" : ""}`,
27360
+ onClick: handleClick,
27361
+ onMouseMove: handleMouseMove,
27362
+ onMouseLeave: () => isPlaying && !controlsPinned && setShowControls(false),
27002
27363
  children: [
27003
27364
  /* @__PURE__ */ jsx("div", { className: "hidden", children: /* @__PURE__ */ jsx(
27004
27365
  HlsVideoPlayer,
27005
27366
  {
27006
27367
  ref: hiddenVideoRef,
27007
27368
  ...videoProps,
27369
+ controls: false,
27008
27370
  onReady: handleVideoReady,
27009
27371
  onPlay: handleVideoPlay,
27010
27372
  onPause: handleVideoPause,
@@ -27014,7 +27376,9 @@ var CroppedHlsVideoPlayer = forwardRef(({
27014
27376
  onLoadedMetadata: handleLoadedMetadata,
27015
27377
  onLoadedData: videoProps.onLoadedData,
27016
27378
  onPlaying: videoProps.onPlaying,
27017
- onLoadingChange: videoProps.onLoadingChange
27379
+ onLoadingChange: videoProps.onLoadingChange,
27380
+ onTimeUpdate: handleTimeUpdate,
27381
+ onDurationChange: handleDurationChange
27018
27382
  }
27019
27383
  ) }),
27020
27384
  /* @__PURE__ */ jsx(
@@ -27031,8 +27395,8 @@ var CroppedHlsVideoPlayer = forwardRef(({
27031
27395
  }
27032
27396
  }
27033
27397
  ),
27034
- !isVideoReady && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27035
- debug && isVideoReady && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 left-2 bg-black/80 text-white text-xs p-2 rounded font-mono", children: [
27398
+ !isVideoReady && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27399
+ debug && isVideoReady && /* @__PURE__ */ 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: [
27036
27400
  /* @__PURE__ */ jsxs("div", { children: [
27037
27401
  "Crop: ",
27038
27402
  crop.x,
@@ -27056,13 +27420,32 @@ var CroppedHlsVideoPlayer = forwardRef(({
27056
27420
  isProcessing ? "Yes" : "No"
27057
27421
  ] })
27058
27422
  ] }),
27059
- onClick && /* @__PURE__ */ jsx(
27060
- PlayPauseIndicator,
27423
+ controls && isVideoReady && /* @__PURE__ */ jsx(
27424
+ VideoControls,
27061
27425
  {
27062
- show: showIndicator,
27063
- isPlaying: indicatorIsPlaying
27064
- },
27065
- indicatorKeyRef.current
27426
+ isPlaying,
27427
+ currentTime,
27428
+ duration,
27429
+ buffered,
27430
+ showControls: controlsPinned || showControls || !isPlaying,
27431
+ controlsPinned,
27432
+ playbackRate,
27433
+ onPlayPause: handleTogglePlay,
27434
+ onSeek: handleSeek,
27435
+ onSeekStart: handleSeekStart,
27436
+ onSeekEnd: handleSeekEnd,
27437
+ onPlaybackRateChange: handlePlaybackRateChange,
27438
+ onTogglePinControls: () => setControlsPinned((prev) => {
27439
+ const next = !prev;
27440
+ if (next) {
27441
+ setShowControls(true);
27442
+ } else {
27443
+ resetControlsTimeout();
27444
+ }
27445
+ return next;
27446
+ }),
27447
+ onToggleFullscreen: handleToggleFullscreen
27448
+ }
27066
27449
  )
27067
27450
  ]
27068
27451
  }
@@ -27601,6 +27984,67 @@ var SilentErrorBoundary = class extends React23__default.Component {
27601
27984
  ] }) });
27602
27985
  }
27603
27986
  };
27987
+ var PlayPauseIndicator = ({
27988
+ show,
27989
+ isPlaying,
27990
+ duration = 600
27991
+ }) => {
27992
+ const [isVisible, setIsVisible] = useState(false);
27993
+ const [isFading, setIsFading] = useState(false);
27994
+ useEffect(() => {
27995
+ if (show) {
27996
+ setIsVisible(true);
27997
+ setIsFading(false);
27998
+ const fadeTimer = setTimeout(() => {
27999
+ setIsFading(true);
28000
+ }, 100);
28001
+ const hideTimer = setTimeout(() => {
28002
+ setIsVisible(false);
28003
+ setIsFading(false);
28004
+ }, duration);
28005
+ return () => {
28006
+ clearTimeout(fadeTimer);
28007
+ clearTimeout(hideTimer);
28008
+ };
28009
+ }
28010
+ }, [show, duration]);
28011
+ if (!isVisible) return null;
28012
+ return /* @__PURE__ */ jsx(
28013
+ "div",
28014
+ {
28015
+ className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10",
28016
+ style: {
28017
+ opacity: isFading ? 0 : 1,
28018
+ transition: `opacity ${duration - 100}ms ease-out`
28019
+ },
28020
+ children: /* @__PURE__ */ jsx("div", { className: "bg-black/70 rounded-full p-6", children: isPlaying ? (
28021
+ // Play icon (triangle)
28022
+ /* @__PURE__ */ jsx(
28023
+ "svg",
28024
+ {
28025
+ xmlns: "http://www.w3.org/2000/svg",
28026
+ viewBox: "0 0 24 24",
28027
+ fill: "white",
28028
+ className: "w-16 h-16",
28029
+ children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z" })
28030
+ }
28031
+ )
28032
+ ) : (
28033
+ // Pause icon (two bars)
28034
+ /* @__PURE__ */ jsx(
28035
+ "svg",
28036
+ {
28037
+ xmlns: "http://www.w3.org/2000/svg",
28038
+ viewBox: "0 0 24 24",
28039
+ fill: "white",
28040
+ className: "w-16 h-16",
28041
+ children: /* @__PURE__ */ jsx("path", { d: "M6 4h4v16H6V4zm8 0h4v16h-4V4z" })
28042
+ }
28043
+ )
28044
+ ) })
28045
+ }
28046
+ );
28047
+ };
27604
28048
  var BackButton = ({
27605
28049
  onClick,
27606
28050
  text = "Back",
@@ -31020,7 +31464,7 @@ function DiagnosisVideoModal({
31020
31464
  }
31021
31465
  loadClip();
31022
31466
  }, [clipId, supabase, transformPlaylistUrls]);
31023
- const formatTime3 = (seconds) => {
31467
+ const formatTime4 = (seconds) => {
31024
31468
  const mins = Math.floor(seconds / 60);
31025
31469
  const secs = Math.floor(seconds % 60);
31026
31470
  return `${mins}:${secs.toString().padStart(2, "0")}`;
@@ -31212,9 +31656,9 @@ function DiagnosisVideoModal({
31212
31656
  }
31213
31657
  ),
31214
31658
  /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
31215
- formatTime3(currentTime),
31659
+ formatTime4(currentTime),
31216
31660
  " / ",
31217
- formatTime3(duration)
31661
+ formatTime4(duration)
31218
31662
  ] }),
31219
31663
  /* @__PURE__ */ jsx(
31220
31664
  "input",
@@ -33185,7 +33629,7 @@ var LinePdfGenerator = ({
33185
33629
  }
33186
33630
  hourEndTime.setSeconds(0);
33187
33631
  hourEndTime.setMilliseconds(0);
33188
- const formatTime3 = (date2) => {
33632
+ const formatTime4 = (date2) => {
33189
33633
  return date2.toLocaleTimeString("en-IN", {
33190
33634
  hour: "2-digit",
33191
33635
  minute: "2-digit",
@@ -33193,7 +33637,7 @@ var LinePdfGenerator = ({
33193
33637
  timeZone: "Asia/Kolkata"
33194
33638
  });
33195
33639
  };
33196
- return `${formatTime3(hourStartTime)} - ${formatTime3(hourEndTime)}`;
33640
+ return `${formatTime4(hourStartTime)} - ${formatTime4(hourEndTime)}`;
33197
33641
  });
33198
33642
  };
33199
33643
  const hourlyTimeRanges = lineInfo.metrics.shift_start ? getHourlyTimeRanges(lineInfo.metrics.shift_start, lineInfo.metrics.shift_end) : [];
@@ -39779,7 +40223,7 @@ var AIAgentView = () => {
39779
40223
  }
39780
40224
  return formattedLines.join("");
39781
40225
  };
39782
- const formatTime3 = (timestamp) => {
40226
+ const formatTime4 = (timestamp) => {
39783
40227
  const date = new Date(timestamp);
39784
40228
  return date.toLocaleTimeString([], {
39785
40229
  hour: "2-digit",
@@ -41043,7 +41487,7 @@ var AIAgentView = () => {
41043
41487
  }
41044
41488
  ),
41045
41489
  /* @__PURE__ */ 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: [
41046
- /* @__PURE__ */ jsx("span", { children: formatTime3(message.created_at) }),
41490
+ /* @__PURE__ */ jsx("span", { children: formatTime4(message.created_at) }),
41047
41491
  message.role === "assistant" && message.id !== -1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
41048
41492
  /* @__PURE__ */ jsx("div", { className: "w-1 h-1 bg-gray-300 rounded-full" }),
41049
41493
  /* @__PURE__ */ jsx("span", { children: "Axel" })