@optifye/dashboard-core 6.9.7 → 6.9.9

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
@@ -8,7 +8,7 @@ var dateFns = require('date-fns');
8
8
  var mixpanel = require('mixpanel-browser');
9
9
  var events = require('events');
10
10
  var supabaseJs = require('@supabase/supabase-js');
11
- var Hls2 = require('hls.js');
11
+ var Hls3 = require('hls.js');
12
12
  var useSWR = require('swr');
13
13
  var motionUtils = require('motion-utils');
14
14
  var motionDom = require('motion-dom');
@@ -17,8 +17,6 @@ var sonner = require('sonner');
17
17
  var recharts = require('recharts');
18
18
  var reactSlot = require('@radix-ui/react-slot');
19
19
  var SelectPrimitive = require('@radix-ui/react-select');
20
- var videojs = require('video.js');
21
- require('video.js/dist/video-js.css');
22
20
  var reactDayPicker = require('react-day-picker');
23
21
  var outline = require('@heroicons/react/24/outline');
24
22
  var solid = require('@heroicons/react/24/solid');
@@ -51,10 +49,9 @@ function _interopNamespace(e) {
51
49
 
52
50
  var React23__namespace = /*#__PURE__*/_interopNamespace(React23);
53
51
  var mixpanel__default = /*#__PURE__*/_interopDefault(mixpanel);
54
- var Hls2__default = /*#__PURE__*/_interopDefault(Hls2);
52
+ var Hls3__default = /*#__PURE__*/_interopDefault(Hls3);
55
53
  var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
56
54
  var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive);
57
- var videojs__default = /*#__PURE__*/_interopDefault(videojs);
58
55
  var html2canvas__default = /*#__PURE__*/_interopDefault(html2canvas);
59
56
  var jsPDF__default = /*#__PURE__*/_interopDefault(jsPDF);
60
57
 
@@ -9019,12 +9016,12 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
9019
9016
  const video = videoRef.current;
9020
9017
  if (!video) return;
9021
9018
  isNativeHlsRef.current = video.canPlayType("application/vnd.apple.mpegurl") === "probably";
9022
- if (Hls2__default.default.isSupported() && !isNativeHlsRef.current) {
9023
- const hls = new Hls2__default.default(HLS_CONFIG);
9019
+ if (Hls3__default.default.isSupported() && !isNativeHlsRef.current) {
9020
+ const hls = new Hls3__default.default(HLS_CONFIG);
9024
9021
  hlsRef.current = hls;
9025
9022
  hls.attachMedia(video);
9026
9023
  hls.loadSource(src);
9027
- hls.on(Hls2__default.default.Events.ERROR, (_, data) => {
9024
+ hls.on(Hls3__default.default.Events.ERROR, (_, data) => {
9028
9025
  if (!data.fatal) return;
9029
9026
  console.error("[HLS] Fatal error:", data.type, data.details);
9030
9027
  if (data.response?.code === 404) {
@@ -9032,8 +9029,8 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
9032
9029
  return;
9033
9030
  }
9034
9031
  switch (data.type) {
9035
- case Hls2__default.default.ErrorTypes.NETWORK_ERROR:
9036
- case Hls2__default.default.ErrorTypes.MEDIA_ERROR:
9032
+ case Hls3__default.default.ErrorTypes.NETWORK_ERROR:
9033
+ case Hls3__default.default.ErrorTypes.MEDIA_ERROR:
9037
9034
  softRestart(`${data.type}: ${data.details}`);
9038
9035
  break;
9039
9036
  default:
@@ -9041,7 +9038,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
9041
9038
  break;
9042
9039
  }
9043
9040
  });
9044
- hls.on(Hls2__default.default.Events.MANIFEST_PARSED, () => {
9041
+ hls.on(Hls3__default.default.Events.MANIFEST_PARSED, () => {
9045
9042
  if (failedUrls.has(src)) {
9046
9043
  console.log(`[HLS] Stream loaded successfully, resetting failure count for: ${src}`);
9047
9044
  failedUrls.delete(src);
@@ -9606,9 +9603,6 @@ var getAllWorkspaceDisplayNamesAsync = async (companyId, lineId) => {
9606
9603
  Object.entries(runtimeWorkspaceDisplayNames).forEach(([lineId2, lineNames]) => {
9607
9604
  Object.entries(lineNames).forEach(([workspaceId, displayName]) => {
9608
9605
  allNames[`${lineId2}_${workspaceId}`] = displayName;
9609
- if (!allNames[workspaceId]) {
9610
- allNames[workspaceId] = displayName;
9611
- }
9612
9606
  });
9613
9607
  });
9614
9608
  return allNames;
@@ -14087,9 +14081,9 @@ var S3VideoPreloader = class {
14087
14081
  this.processQueue();
14088
14082
  };
14089
14083
  if (url.endsWith(".m3u8")) {
14090
- import('hls.js').then(({ default: Hls3 }) => {
14091
- if (Hls3.isSupported()) {
14092
- const hls = new Hls3({
14084
+ import('hls.js').then(({ default: Hls4 }) => {
14085
+ if (Hls4.isSupported()) {
14086
+ const hls = new Hls4({
14093
14087
  maxBufferLength: 10,
14094
14088
  startFragPrefetch: true,
14095
14089
  lowLatencyMode: false,
@@ -14101,7 +14095,7 @@ var S3VideoPreloader = class {
14101
14095
  hls.destroy();
14102
14096
  cleanup();
14103
14097
  }, 4e3);
14104
- hls.on(Hls3.Events.BUFFER_APPENDED, () => {
14098
+ hls.on(Hls4.Events.BUFFER_APPENDED, () => {
14105
14099
  window.clearTimeout(timeout);
14106
14100
  hls.destroy();
14107
14101
  cleanup();
@@ -23105,7 +23099,7 @@ var OutputProgressChartComponent = ({
23105
23099
  ];
23106
23100
  const COLORS = ["#00AB45", "#f3f4f6"];
23107
23101
  const percentage = (currentOutput / targetOutput * 100).toFixed(1);
23108
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full max-w-[200px] sm:max-w-[240px] lg:max-w-[280px] aspect-square", style: { minHeight: "120px", minWidth: "120px" }, children: [
23102
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `w-full h-full flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-square max-h-full", style: { maxWidth: "min(100%, 280px)" }, children: [
23109
23103
  /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.PieChart, { children: /* @__PURE__ */ jsxRuntime.jsx(
23110
23104
  recharts.Pie,
23111
23105
  {
@@ -24737,7 +24731,8 @@ var VideoGridView = React23__namespace.default.memo(({
24737
24731
  displayName: (
24738
24732
  // Create line-aware lookup key: lineId_workspaceName
24739
24733
  // This ensures correct mapping when multiple lines have same workspace names
24740
- displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || displayNames[workspace.workspace_name] || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id)
24734
+ displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || // Always pass line_id to fallback to ensure correct mapping per line
24735
+ getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id)
24741
24736
  ),
24742
24737
  useRAF: canvasConfig?.useRAF,
24743
24738
  compact: !selectedLine,
@@ -24787,7 +24782,8 @@ var MapGridView = React23__namespace.default.memo(({
24787
24782
  efficiency: workspace.efficiency,
24788
24783
  action_count: workspace.action_count
24789
24784
  });
24790
- const displayName = displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || displayNames[workspace.workspace_name] || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24785
+ const displayName = displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || // Always pass line_id to fallback to ensure correct mapping per line
24786
+ getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24791
24787
  const navParams = getWorkspaceNavigationParams(workspaceId, displayName, workspace.line_id);
24792
24788
  router$1.push(`/workspace/${workspaceId}${navParams}`);
24793
24789
  }, [router$1, displayNames]);
@@ -24816,7 +24812,8 @@ var MapGridView = React23__namespace.default.memo(({
24816
24812
  if (!workspace) return null;
24817
24813
  const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
24818
24814
  getPerformanceColor(workspace.efficiency);
24819
- const workspaceDisplayName = displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || displayNames[workspace.workspace_name] || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24815
+ const workspaceDisplayName = displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || // Always pass line_id to fallback to ensure correct mapping per line
24816
+ getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
24820
24817
  return /* @__PURE__ */ jsxRuntime.jsx(
24821
24818
  motion.div,
24822
24819
  {
@@ -26054,12 +26051,6 @@ var AxelNotificationPopup = ({
26054
26051
  };
26055
26052
 
26056
26053
  // src/views/components/workspace/BottlenecksContent.utils.ts
26057
- var formatTime2 = (seconds) => {
26058
- if (isNaN(seconds)) return "0:00";
26059
- const minutes = Math.floor(seconds / 60);
26060
- const remainingSeconds = Math.floor(seconds % 60);
26061
- return `${minutes}:${remainingSeconds < 10 ? "0" : ""}${remainingSeconds}`;
26062
- };
26063
26054
  var getSeverityColor = (severity) => {
26064
26055
  switch (severity) {
26065
26056
  case "low":
@@ -26072,473 +26063,6 @@ var getSeverityColor = (severity) => {
26072
26063
  return "bg-gray-500";
26073
26064
  }
26074
26065
  };
26075
- function Skeleton({ className, ...props }) {
26076
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("animate-pulse rounded-md bg-muted", className), ...props });
26077
- }
26078
- var Select = SelectPrimitive__namespace.Root;
26079
- var SelectGroup = SelectPrimitive__namespace.Group;
26080
- var SelectValue = SelectPrimitive__namespace.Value;
26081
- var SelectTrigger = React23__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
26082
- SelectPrimitive__namespace.Trigger,
26083
- {
26084
- ref,
26085
- className: cn(
26086
- "flex h-11 sm:h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 touch-manipulation",
26087
- className
26088
- ),
26089
- ...props,
26090
- children: [
26091
- children,
26092
- /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Icon, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" }) })
26093
- ]
26094
- }
26095
- ));
26096
- SelectTrigger.displayName = SelectPrimitive__namespace.Trigger.displayName;
26097
- var SelectScrollUpButton = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
26098
- SelectPrimitive__namespace.ScrollUpButton,
26099
- {
26100
- ref,
26101
- className: cn("flex cursor-default items-center justify-center py-1", className),
26102
- ...props,
26103
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-4 w-4" })
26104
- }
26105
- ));
26106
- SelectScrollUpButton.displayName = SelectPrimitive__namespace.ScrollUpButton.displayName;
26107
- var SelectScrollDownButton = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
26108
- SelectPrimitive__namespace.ScrollDownButton,
26109
- {
26110
- ref,
26111
- className: cn("flex cursor-default items-center justify-center py-1", className),
26112
- ...props,
26113
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" })
26114
- }
26115
- ));
26116
- SelectScrollDownButton.displayName = SelectPrimitive__namespace.ScrollDownButton.displayName;
26117
- var SelectContent = React23__namespace.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
26118
- SelectPrimitive__namespace.Content,
26119
- {
26120
- ref,
26121
- className: cn(
26122
- "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
26123
- position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
26124
- className
26125
- ),
26126
- position,
26127
- ...props,
26128
- children: [
26129
- /* @__PURE__ */ jsxRuntime.jsx(SelectScrollUpButton, {}),
26130
- /* @__PURE__ */ jsxRuntime.jsx(
26131
- SelectPrimitive__namespace.Viewport,
26132
- {
26133
- className: cn(
26134
- "p-1",
26135
- position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
26136
- ),
26137
- children
26138
- }
26139
- ),
26140
- /* @__PURE__ */ jsxRuntime.jsx(SelectScrollDownButton, {})
26141
- ]
26142
- }
26143
- ) }));
26144
- SelectContent.displayName = SelectPrimitive__namespace.Content.displayName;
26145
- var SelectLabel = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
26146
- SelectPrimitive__namespace.Label,
26147
- {
26148
- ref,
26149
- className: cn("px-2 py-1.5 text-sm font-semibold", className),
26150
- ...props
26151
- }
26152
- ));
26153
- SelectLabel.displayName = SelectPrimitive__namespace.Label.displayName;
26154
- var SelectItem = React23__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
26155
- SelectPrimitive__namespace.Item,
26156
- {
26157
- ref,
26158
- className: cn(
26159
- "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
26160
- className
26161
- ),
26162
- ...props,
26163
- children: [
26164
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemIndicator, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }) }),
26165
- /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemText, { children })
26166
- ]
26167
- }
26168
- ));
26169
- SelectItem.displayName = SelectPrimitive__namespace.Item.displayName;
26170
- var SelectSeparator = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
26171
- SelectPrimitive__namespace.Separator,
26172
- {
26173
- ref,
26174
- className: cn("-mx-1 my-1 h-px bg-muted", className),
26175
- ...props
26176
- }
26177
- ));
26178
- SelectSeparator.displayName = SelectPrimitive__namespace.Separator.displayName;
26179
- var LoadingOverlay = ({
26180
- isVisible,
26181
- message = "Loading...",
26182
- className
26183
- }) => {
26184
- if (!isVisible) return null;
26185
- return /* @__PURE__ */ jsxRuntime.jsx(
26186
- motion.div,
26187
- {
26188
- initial: { opacity: 0 },
26189
- animate: { opacity: 1 },
26190
- exit: { opacity: 0 },
26191
- transition: { duration: 0.2 },
26192
- className: `fixed inset-0 z-[100] flex items-center justify-center bg-black/30 backdrop-blur-sm ${className || ""}`,
26193
- "aria-modal": "true",
26194
- role: "dialog",
26195
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center space-y-3 rounded-lg bg-white p-8 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message }) })
26196
- }
26197
- );
26198
- };
26199
- var LoadingOverlay_default = LoadingOverlay;
26200
- var TimeDisplay = ({ className, variant = "default" }) => {
26201
- const { dateTimeConfig } = useDashboardConfig();
26202
- const [time2, setTime] = React23.useState("");
26203
- const dbTimezone = useAppTimezone();
26204
- const timezoneToDisplay = dbTimezone || dateTimeConfig?.defaultTimezone || "UTC";
26205
- const localeToUse = dateTimeConfig?.defaultLocale || "en-US";
26206
- const timeSuffix = "";
26207
- React23.useEffect(() => {
26208
- const updateTime = () => {
26209
- const now2 = /* @__PURE__ */ new Date();
26210
- const effectiveFormatOptions = {
26211
- hour: "2-digit",
26212
- minute: "2-digit",
26213
- second: "2-digit",
26214
- hour12: true,
26215
- timeZone: timezoneToDisplay,
26216
- ...dateTimeConfig?.timeFormatOptions || {}
26217
- // Allow override from config
26218
- };
26219
- try {
26220
- setTime(new Intl.DateTimeFormat(localeToUse, effectiveFormatOptions).format(now2));
26221
- } catch (e) {
26222
- console.error("Error formatting time:", e);
26223
- setTime("Error");
26224
- }
26225
- };
26226
- updateTime();
26227
- const interval = setInterval(updateTime, 1e3);
26228
- return () => clearInterval(interval);
26229
- }, [timezoneToDisplay, dateTimeConfig?.timeFormatOptions, localeToUse]);
26230
- if (!time2) return null;
26231
- if (variant === "minimal") {
26232
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: className || "", children: [
26233
- time2,
26234
- " ",
26235
- timeSuffix
26236
- ] });
26237
- }
26238
- return /* @__PURE__ */ jsxRuntime.jsxs(
26239
- motion.div,
26240
- {
26241
- initial: { opacity: 0, y: -5 },
26242
- animate: { opacity: 1, y: 0 },
26243
- transition: { duration: 0.3 },
26244
- className: `flex items-center space-x-1.5 bg-white/60 backdrop-blur-sm px-2 py-0.5 rounded-md shadow-xs ${className || ""}`,
26245
- children: [
26246
- /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-3 w-3 text-[var(--primary-DEFAULT)]", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z", clipRule: "evenodd" }) }),
26247
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs sm:text-[11px] font-medium text-gray-800 tabular-nums tracking-tight", children: [
26248
- time2,
26249
- " ",
26250
- timeSuffix
26251
- ] })
26252
- ]
26253
- }
26254
- );
26255
- };
26256
- var TimeDisplay_default = TimeDisplay;
26257
- var DateDisplay = ({ className, variant = "default" }) => {
26258
- const { dateTimeConfig } = useDashboardConfig();
26259
- const [date, setDate] = React23.useState("");
26260
- const timezoneToDisplay = dateTimeConfig?.defaultTimezone || "UTC";
26261
- const localeToUse = dateTimeConfig?.defaultLocale || "en-US";
26262
- React23.useEffect(() => {
26263
- const getCurrentFormattedDate = () => {
26264
- const now2 = /* @__PURE__ */ new Date();
26265
- const effectiveFormatOptions = {
26266
- weekday: "short",
26267
- day: "numeric",
26268
- month: "short",
26269
- timeZone: timezoneToDisplay,
26270
- ...dateTimeConfig?.dateFormatOptions || {}
26271
- // Allow override from config
26272
- };
26273
- try {
26274
- return new Intl.DateTimeFormat(localeToUse, effectiveFormatOptions).format(now2);
26275
- } catch (e) {
26276
- console.error("Error formatting date:", e);
26277
- return "Error";
26278
- }
26279
- };
26280
- const updateDate = () => {
26281
- setDate(getCurrentFormattedDate());
26282
- };
26283
- updateDate();
26284
- const interval = setInterval(() => {
26285
- const currentDateStr = getCurrentFormattedDate();
26286
- if (currentDateStr !== date) {
26287
- updateDate();
26288
- }
26289
- }, 60 * 1e3);
26290
- return () => clearInterval(interval);
26291
- }, [date, timezoneToDisplay, dateTimeConfig?.dateFormatOptions, localeToUse]);
26292
- if (!date) return null;
26293
- if (variant === "minimal") {
26294
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: className || "", children: date });
26295
- }
26296
- return /* @__PURE__ */ jsxRuntime.jsxs(
26297
- motion.div,
26298
- {
26299
- initial: { opacity: 0, y: -5 },
26300
- animate: { opacity: 1, y: 0 },
26301
- transition: { duration: 0.3 },
26302
- className: `flex items-center space-x-1.5 bg-white/60 backdrop-blur-sm px-2 py-0.5 rounded-md shadow-xs ${className || ""}`,
26303
- children: [
26304
- /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-3 w-3 text-blue-600", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", clipRule: "evenodd" }) }),
26305
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-gray-800 tracking-tight", children: date })
26306
- ]
26307
- }
26308
- );
26309
- };
26310
- var DateDisplay_default = DateDisplay;
26311
- var Card3 = Card2;
26312
- var CardHeader3 = CardHeader2;
26313
- var CardTitle3 = CardTitle2;
26314
- var CardContent3 = CardContent2;
26315
- var MetricCard2 = ({ title, value, unit = "", trend = null }) => {
26316
- const getTrendColor = (trendValue) => {
26317
- if (trendValue === null || trendValue === void 0) return "";
26318
- return trendValue > 0 ? "text-green-500" : trendValue < 0 ? "text-red-500" : "text-gray-500";
26319
- };
26320
- const getTrendSymbol = (trendValue) => {
26321
- if (trendValue === null || trendValue === void 0) return "";
26322
- return trendValue > 0 ? "\u2191" : trendValue < 0 ? "\u2193" : "";
26323
- };
26324
- return /* @__PURE__ */ jsxRuntime.jsxs(Card3, { className: "bg-white", children: [
26325
- /* @__PURE__ */ jsxRuntime.jsx(CardHeader3, { className: "pb-2", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle3, { className: "text-sm font-medium text-gray-500", children: title }) }),
26326
- /* @__PURE__ */ jsxRuntime.jsx(CardContent3, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between", children: [
26327
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl font-semibold", children: [
26328
- value,
26329
- unit && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-sm text-gray-500", children: unit })
26330
- ] }),
26331
- trend !== null && trend !== void 0 && // Check trend for null/undefined before accessing
26332
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex items-center ${getTrendColor(trend)}`, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm", children: [
26333
- getTrendSymbol(trend),
26334
- " ",
26335
- Math.abs(trend),
26336
- "%"
26337
- ] }) })
26338
- ] }) })
26339
- ] });
26340
- };
26341
- var MetricCard_default = MetricCard2;
26342
- var TimePickerDropdown = ({
26343
- value,
26344
- onChange,
26345
- placeholder = "Select time",
26346
- className = "",
26347
- disabled = false
26348
- }) => {
26349
- const [isOpen, setIsOpen] = React23.useState(false);
26350
- const [searchTerm, setSearchTerm] = React23.useState("");
26351
- const dropdownRef = React23.useRef(null);
26352
- const inputRef = React23.useRef(null);
26353
- const generateTimeSlots = () => {
26354
- const slots = [];
26355
- for (let hour = 0; hour < 24; hour++) {
26356
- for (let minute = 0; minute < 60; minute += 15) {
26357
- const time24 = `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
26358
- const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
26359
- const ampm = hour < 12 ? "AM" : "PM";
26360
- const time12 = `${hour12}:${minute.toString().padStart(2, "0")} ${ampm}`;
26361
- slots.push({ value: time24, label: time12 });
26362
- }
26363
- }
26364
- return slots;
26365
- };
26366
- const timeSlots = generateTimeSlots();
26367
- const filteredSlots = timeSlots.filter(
26368
- (slot) => slot.label.toLowerCase().includes(searchTerm.toLowerCase())
26369
- );
26370
- const getDisplayValue = (value2) => {
26371
- if (!value2) return "";
26372
- const normalizedValue = value2.substring(0, 5);
26373
- const slot = timeSlots.find((s) => s.value === normalizedValue);
26374
- return slot ? slot.label : value2;
26375
- };
26376
- React23.useEffect(() => {
26377
- const handleClickOutside = (event) => {
26378
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
26379
- setIsOpen(false);
26380
- setSearchTerm("");
26381
- }
26382
- };
26383
- document.addEventListener("mousedown", handleClickOutside);
26384
- return () => document.removeEventListener("mousedown", handleClickOutside);
26385
- }, []);
26386
- const handleKeyDown = (e) => {
26387
- if (e.key === "Escape") {
26388
- setIsOpen(false);
26389
- setSearchTerm("");
26390
- } else if (e.key === "Enter") {
26391
- e.preventDefault();
26392
- if (filteredSlots.length > 0) {
26393
- onChange(filteredSlots[0].value);
26394
- setIsOpen(false);
26395
- setSearchTerm("");
26396
- }
26397
- }
26398
- };
26399
- const handleSelect = (timeValue) => {
26400
- onChange(timeValue);
26401
- setIsOpen(false);
26402
- setSearchTerm("");
26403
- };
26404
- const handleToggle = () => {
26405
- if (disabled) return;
26406
- setIsOpen(!isOpen);
26407
- if (!isOpen) {
26408
- setTimeout(() => inputRef.current?.focus(), 100);
26409
- }
26410
- };
26411
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className}`, ref: dropdownRef, children: [
26412
- /* @__PURE__ */ jsxRuntime.jsx(
26413
- "button",
26414
- {
26415
- type: "button",
26416
- onClick: handleToggle,
26417
- disabled,
26418
- className: `
26419
- w-full px-3 py-2 text-left bg-white border border-gray-300 rounded-md shadow-sm
26420
- focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
26421
- hover:border-gray-400 transition-colors duration-200
26422
- ${disabled ? "bg-gray-50 cursor-not-allowed" : "cursor-pointer"}
26423
- ${isOpen ? "ring-2 ring-blue-500 border-blue-500" : ""}
26424
- `,
26425
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
26426
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
26427
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4 text-gray-400" }),
26428
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-sm ${value ? "text-gray-900" : "text-gray-500"}`, children: value ? getDisplayValue(value) : placeholder })
26429
- ] }),
26430
- /* @__PURE__ */ jsxRuntime.jsx(
26431
- lucideReact.ChevronDown,
26432
- {
26433
- className: `h-4 w-4 text-gray-400 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`
26434
- }
26435
- )
26436
- ] })
26437
- }
26438
- ),
26439
- isOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-hidden", children: [
26440
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx(
26441
- "input",
26442
- {
26443
- ref: inputRef,
26444
- type: "text",
26445
- placeholder: "Search time...",
26446
- value: searchTerm,
26447
- onChange: (e) => setSearchTerm(e.target.value),
26448
- onKeyDown: handleKeyDown,
26449
- className: "w-full px-3 py-2 text-sm border border-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
26450
- }
26451
- ) }),
26452
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-y-auto max-h-48", children: filteredSlots.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-gray-500 text-center", children: "No times found" }) : filteredSlots.map((slot) => /* @__PURE__ */ jsxRuntime.jsx(
26453
- "button",
26454
- {
26455
- type: "button",
26456
- onClick: () => handleSelect(slot.value),
26457
- className: `
26458
- w-full px-3 py-2 text-left text-sm hover:bg-blue-50 hover:text-blue-600
26459
- transition-colors duration-150 border-b border-gray-100 last:border-b-0
26460
- ${slot.value === value ? "bg-blue-50 text-blue-600 font-medium" : "text-gray-700"}
26461
- `,
26462
- children: slot.label
26463
- },
26464
- slot.value
26465
- )) })
26466
- ] })
26467
- ] });
26468
- };
26469
- var SilentErrorBoundary = class extends React23__namespace.default.Component {
26470
- constructor(props) {
26471
- super(props);
26472
- this.handleClearAndReload = () => {
26473
- console.log("[ErrorBoundary] User initiated reset");
26474
- if (typeof window !== "undefined") {
26475
- try {
26476
- localStorage.clear();
26477
- sessionStorage.clear();
26478
- console.log("[ErrorBoundary] Cleared all storage");
26479
- } catch (error) {
26480
- console.error("[ErrorBoundary] Failed to clear storage:", error);
26481
- }
26482
- }
26483
- window.location.href = "/login";
26484
- };
26485
- this.state = {
26486
- hasError: false,
26487
- errorCount: 0,
26488
- lastError: null,
26489
- errorInfo: null
26490
- };
26491
- }
26492
- static getDerivedStateFromError(error) {
26493
- return { hasError: true };
26494
- }
26495
- componentDidCatch(error, errorInfo) {
26496
- console.error("[ErrorBoundary] Caught render error:", {
26497
- error: error.message,
26498
- stack: error.stack,
26499
- componentStack: errorInfo.componentStack,
26500
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
26501
- });
26502
- this.setState((prev) => ({
26503
- errorCount: prev.errorCount + 1,
26504
- lastError: error,
26505
- errorInfo
26506
- }));
26507
- try {
26508
- if (typeof window !== "undefined" && window.mixpanel) {
26509
- window.mixpanel.track("React Render Error", {
26510
- error: error.message,
26511
- component: errorInfo.componentStack?.split("\n")[1] || "unknown",
26512
- errorCount: this.state.errorCount + 1
26513
- });
26514
- }
26515
- } catch (analyticsError) {
26516
- console.warn("[ErrorBoundary] Analytics tracking failed:", analyticsError);
26517
- }
26518
- }
26519
- render() {
26520
- if (!this.state.hasError) {
26521
- return this.props.children;
26522
- }
26523
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen w-screen items-center justify-center bg-slate-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center space-y-6 text-center", children: [
26524
- /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading Dashboard..." }),
26525
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "Taking longer than usual..." }),
26526
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
26527
- "a",
26528
- {
26529
- href: "#",
26530
- onClick: (e) => {
26531
- e.preventDefault();
26532
- this.handleClearAndReload();
26533
- },
26534
- className: "text-xs text-gray-400 hover:text-gray-600 underline transition-colors",
26535
- children: "Reset and try again"
26536
- }
26537
- ) }),
26538
- process.env.NODE_ENV === "development" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400 italic mt-4", children: "Check console for error details" })
26539
- ] }) });
26540
- }
26541
- };
26542
26066
  var PlayPauseIndicator = ({
26543
26067
  show,
26544
26068
  isPlaying,
@@ -26601,37 +26125,33 @@ var PlayPauseIndicator = ({
26601
26125
  );
26602
26126
  };
26603
26127
  var ERROR_MAPPING = {
26604
- 1: {
26605
- // MEDIA_ERR_ABORTED
26606
- code: 1,
26607
- type: "recoverable" /* RECOVERABLE */,
26608
- message: "Video loading was interrupted",
26609
- canRetry: true
26610
- },
26611
- 2: {
26612
- // MEDIA_ERR_NETWORK
26128
+ "networkError": {
26613
26129
  code: 2,
26614
- type: "recoverable" /* RECOVERABLE */,
26130
+ type: "recoverable",
26615
26131
  message: "Network error - please check your internet connection",
26616
26132
  canRetry: true
26617
26133
  },
26618
- 3: {
26619
- // MEDIA_ERR_DECODE
26134
+ "mediaError": {
26620
26135
  code: 3,
26621
- type: "non_recoverable" /* NON_RECOVERABLE */,
26136
+ type: "non_recoverable",
26622
26137
  message: "Stream corrupted due to internet connection",
26623
26138
  canRetry: false
26624
26139
  },
26625
- 4: {
26626
- // MEDIA_ERR_SRC_NOT_SUPPORTED
26140
+ "muxError": {
26141
+ code: 3,
26142
+ type: "non_recoverable",
26143
+ message: "Error processing media stream",
26144
+ canRetry: false
26145
+ },
26146
+ "otherError": {
26627
26147
  code: 4,
26628
- type: "non_recoverable" /* NON_RECOVERABLE */,
26148
+ type: "non_recoverable",
26629
26149
  message: "Video format not supported by your browser. Please use Google Chrome.",
26630
26150
  canRetry: false
26631
26151
  }
26632
26152
  };
26633
- var videoPlayerStyles = `
26634
- .video-player-container {
26153
+ var hlsVideoPlayerStyles = `
26154
+ .hls-video-player-container {
26635
26155
  width: 100%;
26636
26156
  height: 100%;
26637
26157
  background-color: #000;
@@ -26639,32 +26159,18 @@ var videoPlayerStyles = `
26639
26159
  align-items: center;
26640
26160
  justify-content: center;
26641
26161
  }
26642
-
26643
- /* Center the video player and maintain aspect ratio */
26644
- .video-js {
26162
+
26163
+ /* Center the video and maintain aspect ratio */
26164
+ .hls-video-element {
26645
26165
  width: 100%;
26646
26166
  height: 100%;
26647
26167
  max-width: 100%;
26648
26168
  max-height: 100%;
26649
- }
26650
-
26651
- /* Ensure video maintains aspect ratio and shows fully */
26652
- .video-js .vjs-tech {
26653
- position: absolute;
26654
- top: 0;
26655
- left: 0;
26656
- width: 100%;
26657
- height: 100%;
26658
26169
  object-fit: contain;
26659
26170
  }
26660
-
26661
- /* Hide default Video.js loading spinner */
26662
- .video-js .vjs-loading-spinner {
26663
- display: none !important;
26664
- }
26665
-
26171
+
26666
26172
  /* Custom loading indicator styles */
26667
- .video-player-loading {
26173
+ .hls-video-player-loading {
26668
26174
  position: absolute;
26669
26175
  top: 50%;
26670
26176
  left: 50%;
@@ -26673,15 +26179,41 @@ var videoPlayerStyles = `
26673
26179
  }
26674
26180
  `;
26675
26181
  if (typeof document !== "undefined") {
26676
- const styleId = "video-player-custom-styles";
26182
+ const styleId = "hls-video-player-custom-styles";
26677
26183
  if (!document.getElementById(styleId)) {
26678
26184
  const style = document.createElement("style");
26679
26185
  style.id = styleId;
26680
- style.textContent = videoPlayerStyles;
26186
+ style.textContent = hlsVideoPlayerStyles;
26681
26187
  document.head.appendChild(style);
26682
26188
  }
26683
26189
  }
26684
- var VideoPlayer = React23__namespace.default.forwardRef(({
26190
+ var BASE_HLS_CONFIG = {
26191
+ maxBufferLength: 3,
26192
+ maxMaxBufferLength: 8,
26193
+ maxBufferSize: 50 * 1e3 * 1e3,
26194
+ maxBufferHole: 0.25,
26195
+ manifestLoadingTimeOut: 15e3,
26196
+ manifestLoadingMaxRetry: 3,
26197
+ manifestLoadingRetryDelay: 500,
26198
+ levelLoadingTimeOut: 6e4,
26199
+ levelLoadingMaxRetry: 5,
26200
+ levelLoadingRetryDelay: 500,
26201
+ fragLoadingTimeOut: 6e4,
26202
+ fragLoadingMaxRetry: 5,
26203
+ fragLoadingRetryDelay: 500,
26204
+ startPosition: -1,
26205
+ debug: false,
26206
+ enableWorker: true,
26207
+ lowLatencyMode: false,
26208
+ progressive: true,
26209
+ abrEwmaSlowLive: 9,
26210
+ abrEwmaFastLive: 3,
26211
+ abrBandWidthFactor: 0.95,
26212
+ abrBandWidthUpFactor: 0.7,
26213
+ abrMaxWithRealBitrate: false,
26214
+ abrEwmaDefaultEstimate: 5e7
26215
+ };
26216
+ var HlsVideoPlayer = React23.forwardRef(({
26685
26217
  src,
26686
26218
  poster,
26687
26219
  autoplay = false,
@@ -26690,7 +26222,9 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
26690
26222
  muted = false,
26691
26223
  playsInline = true,
26692
26224
  className = "",
26225
+ hlsConfig = {},
26693
26226
  options = {},
26227
+ // Backward compatibility with Video.js
26694
26228
  externalLoadingControl = false,
26695
26229
  onLoadingChange,
26696
26230
  onReady,
@@ -26708,321 +26242,396 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
26708
26242
  onSeeked,
26709
26243
  onClick
26710
26244
  }, ref) => {
26245
+ const videoContainerRef = React23.useRef(null);
26711
26246
  const videoRef = React23.useRef(null);
26712
- const playerRef = React23.useRef(null);
26247
+ const hlsRef = React23.useRef(null);
26248
+ const blobUrlRef = React23.useRef(null);
26713
26249
  const [isReady, setIsReady] = React23.useState(false);
26714
26250
  const [isLoading, setIsLoading] = React23.useState(true);
26715
26251
  const [showIndicator, setShowIndicator] = React23.useState(false);
26716
26252
  const [indicatorIsPlaying, setIndicatorIsPlaying] = React23.useState(false);
26717
26253
  const indicatorKeyRef = React23.useRef(0);
26718
- const defaultOptions = {
26719
- controls: false,
26720
- // Always disable Video.js controls - we use custom controls
26721
- // Don't use fluid or fill - let the video maintain its natural aspect ratio
26722
- fluid: false,
26723
- // Disable fluid mode to prevent stretching
26724
- responsive: false,
26725
- // Disable responsive mode
26726
- fill: false,
26727
- // Don't fill - maintain aspect ratio
26728
- playsinline: playsInline,
26729
- preload: "metadata",
26730
- autoplay: autoplay ? "any" : false,
26731
- muted,
26732
- loop,
26733
- poster,
26734
- // Optimized HLS configuration for VOD content with LARGE SEGMENTS (40MB)
26735
- // Strategy: Aggressive buffer reduction for faster startup with large segments
26736
- html5: {
26737
- vhs: {
26738
- // VHS (Video HTTP Streaming) options for HLS
26739
- withCredentials: false,
26740
- handleManifestRedirect: true,
26741
- overrideNative: !videojs__default.default.browser.IS_SAFARI,
26742
- // Use native HLS on Safari
26743
- enableLowInitialPlaylist: true,
26744
- smoothQualityChange: true,
26745
- // Optimized bandwidth and buffering for VOD
26746
- bandwidth: 5e7,
26747
- // Start with high bandwidth assumption (50 Mbps)
26748
- initialBandwidth: 5e7,
26749
- // Assume good connection initially
26750
- limitRenditionByPlayerDimensions: true,
26751
- // Buffer configuration optimized for large segments (40MB each)
26752
- maxBufferLength: 3,
26753
- // Start playing with just 3 seconds ahead
26754
- maxMaxBufferLength: 8,
26755
- // Maximum 8 seconds buffer for faster startup
26756
- maxBufferSize: 50 * 1e3 * 1e3,
26757
- // 50MB max buffer size (1.25 segments)
26758
- maxBufferHole: 0.25,
26759
- // Smaller holes for better continuity
26760
- bufferBasedABR: false,
26761
- // Disable for more predictable behavior
26762
- // Segment loading optimization for large segments (40MB)
26763
- maxPlaylistRetries: 3,
26764
- playlistRetryDelay: 500,
26765
- // 500ms between retries
26766
- playlistExclusionDuration: 60,
26767
- segmentLoadingRetryAttempts: 5,
26768
- // More retries for large segments
26769
- segmentLoadingRetryDelay: 500,
26770
- // Faster retry for responsiveness
26771
- segmentLoadingTimeOut: 6e4,
26772
- // 60s timeout for 40MB segments
26773
- manifestLoadingTimeOut: 15e3,
26774
- // 15s timeout for manifest
26775
- // Performance optimizations
26776
- experimentalBufferBasedCodecSwitching: true,
26777
- experimentalCacheEncryptionKeys: true,
26778
- handlePartialData: true,
26779
- allowSeeksWithinUnsafeLiveWindow: false,
26780
- // VOD content
26781
- experimentalLLHLS: false,
26782
- // Disable Low Latency HLS for VOD
26783
- // Connection settings
26784
- // Chrome 130+ started throwing "Cannot perform Construct on a detached ArrayBuffer"
26785
- // whenever the transmux worker tried to rehydrate transferred buffers that originated
26786
- // from Blob-based playlists. Disabling the worker keeps playback stable at the cost
26787
- // of slightly higher main-thread usage, which is acceptable for the dashboard usage.
26788
- enableWorker: false,
26789
- progressive: true,
26790
- // Progressive download
26791
- // Adaptive bitrate settings (if multi-quality available)
26792
- abrEwmaFastLive: 3,
26793
- abrEwmaSlowLive: 9,
26794
- abrBandWidthFactor: 0.95,
26795
- abrBandWidthUpFactor: 0.7,
26796
- abrMaxWithRealBitrate: false,
26797
- // Request options optimized for large segments
26798
- requestOptions: {
26799
- timeout: 9e4,
26800
- // 90s timeout for 40MB segments
26801
- maxRetry: 5
26802
- // More retries for reliability
26803
- }
26804
- },
26805
- nativeVideoTracks: false,
26806
- nativeAudioTracks: false,
26807
- nativeTextTracks: false
26808
- },
26809
- // Improved seeking and scrubbing
26810
- inactivityTimeout: 3e3,
26811
- // Better error handling
26812
- errorDisplay: false,
26813
- // We'll handle errors with callbacks
26814
- // Fullscreen options
26815
- fullscreen: {
26816
- options: {
26817
- navigationUI: "hide"
26818
- }
26819
- },
26820
- ...options
26821
- };
26822
- const initializePlayer = React23.useCallback(() => {
26823
- if (!videoRef.current || playerRef.current) return;
26824
- const videoElement = document.createElement("video-js");
26825
- videoElement.className = "vjs-default-skin";
26826
- videoRef.current.appendChild(videoElement);
26827
- const player = videojs__default.default(videoElement, defaultOptions);
26828
- playerRef.current = player;
26829
- player.ready(() => {
26830
- setIsReady(true);
26831
- onReady?.(player);
26832
- });
26833
- player.on("play", () => onPlay?.(player));
26834
- player.on("pause", () => onPause?.(player));
26835
- player.on("playing", () => onPlaying?.(player));
26836
- player.on("timeupdate", () => {
26837
- const currentTime2 = player.currentTime() || 0;
26838
- onTimeUpdate?.(player, currentTime2);
26839
- });
26840
- player.on("durationchange", () => {
26841
- const duration2 = player.duration() || 0;
26842
- onDurationChange?.(player, duration2);
26843
- });
26844
- player.on("ended", () => onEnded?.(player));
26845
- player.on("loadstart", () => {
26846
- setIsLoading(true);
26847
- onLoadingChange?.(true);
26848
- onLoadStart?.(player);
26849
- });
26850
- player.on("loadeddata", () => {
26851
- setIsLoading(false);
26852
- onLoadingChange?.(false);
26853
- onLoadedData?.(player);
26854
- });
26855
- player.on("waiting", () => {
26856
- setIsLoading(true);
26857
- onLoadingChange?.(true);
26858
- });
26859
- player.on("playing", () => {
26860
- setIsLoading(false);
26861
- onLoadingChange?.(false);
26862
- });
26863
- player.on("loadedmetadata", () => {
26864
- onLoadedMetadata?.(player);
26865
- });
26866
- player.on("seeking", () => onSeeking?.(player));
26867
- player.on("seeked", () => onSeeked?.(player));
26868
- player.on("error", () => {
26869
- const error = player.error();
26870
- const errorCode = error?.code ?? 0;
26871
- const errorInfo = ERROR_MAPPING[errorCode] || {
26872
- code: errorCode || 0,
26873
- type: "non_recoverable" /* NON_RECOVERABLE */,
26874
- message: "Unknown playback error occurred",
26875
- canRetry: false
26876
- };
26877
- console.error("[VideoPlayer] Video.js error:", {
26878
- code: errorCode,
26879
- type: errorInfo.type,
26880
- message: errorInfo.message,
26881
- canRetry: errorInfo.canRetry,
26882
- originalError: error
26883
- });
26884
- onError?.(player, errorInfo);
26885
- });
26886
- if (src) {
26887
- const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
26888
- let videoSrc = src;
26889
- let blobUrl = null;
26890
- if (src.startsWith("#EXTM3U")) {
26891
- const safariMode = isSafari();
26892
- const browserName = getBrowserName();
26893
- if (safariMode) {
26894
- const clipIdMatch = src.match(/# Clip ID: ([a-f0-9-]+)/i);
26895
- if (clipIdMatch) {
26896
- videoSrc = `/api/clips/playlist/${clipIdMatch[1]}`;
26897
- console.log(`[VideoPlayer] Safari detected (${browserName}) - using playlist proxy URL:`, videoSrc);
26898
- } else {
26899
- console.warn("[VideoPlayer] Safari detected but no clip ID found in playlist, trying blob URL fallback");
26900
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
26901
- blobUrl = URL.createObjectURL(blob);
26902
- videoSrc = blobUrl;
26903
- player._blobUrl = videoSrc;
26904
- }
26905
- } else {
26906
- const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
26907
- blobUrl = URL.createObjectURL(blob);
26908
- videoSrc = blobUrl;
26909
- console.log(`[VideoPlayer] Non-Safari browser (${browserName}) - using blob URL for optimal performance`);
26910
- player._blobUrl = videoSrc;
26911
- }
26912
- }
26913
- player.src({
26914
- src: videoSrc,
26915
- type: isHLS ? "application/x-mpegURL" : "video/mp4"
26916
- });
26917
- }
26254
+ const eventCallbacksRef = React23.useRef({
26255
+ onReady,
26256
+ onPlay,
26257
+ onPause,
26258
+ onPlaying,
26259
+ onTimeUpdate,
26260
+ onDurationChange,
26261
+ onEnded,
26262
+ onError,
26263
+ onLoadStart,
26264
+ onLoadedMetadata,
26265
+ onLoadedData,
26266
+ onSeeking,
26267
+ onSeeked,
26268
+ onLoadingChange
26269
+ });
26270
+ React23.useEffect(() => {
26271
+ eventCallbacksRef.current = {
26272
+ onReady,
26273
+ onPlay,
26274
+ onPause,
26275
+ onPlaying,
26276
+ onTimeUpdate,
26277
+ onDurationChange,
26278
+ onEnded,
26279
+ onError,
26280
+ onLoadStart,
26281
+ onLoadedMetadata,
26282
+ onLoadedData,
26283
+ onSeeking,
26284
+ onSeeked,
26285
+ onLoadingChange
26286
+ };
26918
26287
  }, [
26919
- src,
26920
- defaultOptions,
26921
26288
  onReady,
26922
26289
  onPlay,
26923
26290
  onPause,
26291
+ onPlaying,
26924
26292
  onTimeUpdate,
26925
26293
  onDurationChange,
26926
26294
  onEnded,
26927
26295
  onError,
26928
26296
  onLoadStart,
26929
26297
  onLoadedMetadata,
26298
+ onLoadedData,
26930
26299
  onSeeking,
26931
- onSeeked
26300
+ onSeeked,
26301
+ onLoadingChange
26932
26302
  ]);
26303
+ const stableHlsConfigRef = React23.useRef(hlsConfig);
26304
+ const stableOptionsRef = React23.useRef(options);
26305
+ const configSignatureRef = React23.useRef("");
26306
+ const [configVersion, setConfigVersion] = React23.useState(0);
26933
26307
  React23.useEffect(() => {
26934
- if (playerRef.current && src) {
26935
- const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
26936
- let videoSrc = src;
26937
- let blobUrl = null;
26308
+ const serialized = JSON.stringify({
26309
+ hlsConfig: hlsConfig || null,
26310
+ options: options || null
26311
+ });
26312
+ if (!configSignatureRef.current) {
26313
+ configSignatureRef.current = serialized;
26314
+ stableHlsConfigRef.current = hlsConfig;
26315
+ stableOptionsRef.current = options;
26316
+ return;
26317
+ }
26318
+ if (configSignatureRef.current !== serialized) {
26319
+ configSignatureRef.current = serialized;
26320
+ stableHlsConfigRef.current = hlsConfig;
26321
+ stableOptionsRef.current = options;
26322
+ setConfigVersion((prev) => prev + 1);
26323
+ }
26324
+ }, [hlsConfig, options]);
26325
+ const cleanupBlobUrl = React23.useCallback(() => {
26326
+ if (blobUrlRef.current) {
26327
+ URL.revokeObjectURL(blobUrlRef.current);
26328
+ blobUrlRef.current = null;
26329
+ }
26330
+ }, []);
26331
+ const dispose = React23.useCallback(() => {
26332
+ if (hlsRef.current) {
26333
+ hlsRef.current.destroy();
26334
+ hlsRef.current = null;
26335
+ }
26336
+ cleanupBlobUrl();
26337
+ setIsReady(false);
26338
+ }, [cleanupBlobUrl]);
26339
+ const playerLikeObject = React23.useCallback(() => {
26340
+ return {
26341
+ el: () => videoRef.current,
26342
+ currentTime: () => videoRef.current?.currentTime || 0,
26343
+ duration: () => videoRef.current?.duration || 0,
26344
+ paused: () => videoRef.current?.paused ?? true,
26345
+ play: () => videoRef.current?.play(),
26346
+ pause: () => videoRef.current?.pause(),
26347
+ muted: (val) => {
26348
+ if (videoRef.current) {
26349
+ if (val !== void 0) videoRef.current.muted = val;
26350
+ return videoRef.current.muted;
26351
+ }
26352
+ return false;
26353
+ },
26354
+ volume: (val) => {
26355
+ if (videoRef.current) {
26356
+ if (val !== void 0) videoRef.current.volume = val;
26357
+ return videoRef.current.volume;
26358
+ }
26359
+ return 1;
26360
+ },
26361
+ error: () => null,
26362
+ dispose: () => dispose()
26363
+ };
26364
+ }, [dispose]);
26365
+ const initializePlayer = React23.useCallback(() => {
26366
+ if (!videoRef.current || !src) return;
26367
+ const video = videoRef.current;
26368
+ const player = playerLikeObject();
26369
+ const mergedHlsConfig = {
26370
+ ...BASE_HLS_CONFIG,
26371
+ ...stableHlsConfigRef.current || {},
26372
+ ...stableOptionsRef.current || {}
26373
+ };
26374
+ cleanupBlobUrl();
26375
+ const isHLS = src.endsWith(".m3u8") || src.startsWith("#EXTM3U");
26376
+ if (isHLS) {
26938
26377
  if (src.startsWith("#EXTM3U")) {
26939
26378
  const safariMode = isSafari();
26940
26379
  const browserName = getBrowserName();
26941
26380
  if (safariMode) {
26942
26381
  const clipIdMatch = src.match(/# Clip ID: ([a-f0-9-]+)/i);
26943
26382
  if (clipIdMatch) {
26944
- videoSrc = `/api/clips/playlist/${clipIdMatch[1]}`;
26945
- console.log(`[VideoPlayer] Safari detected (${browserName}) - using playlist proxy URL for source update:`, videoSrc);
26383
+ const proxyUrl = `/api/clips/playlist/${clipIdMatch[1]}`;
26384
+ console.log(`[HlsVideoPlayer] Safari detected (${browserName}) - using playlist proxy URL:`, proxyUrl);
26385
+ video.src = proxyUrl;
26946
26386
  } else {
26947
- console.warn("[VideoPlayer] Safari detected but no clip ID found in playlist (source update), trying blob URL fallback");
26387
+ console.warn("[HlsVideoPlayer] Safari detected but no clip ID found in playlist, trying blob URL fallback");
26948
26388
  const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
26949
- blobUrl = URL.createObjectURL(blob);
26950
- videoSrc = blobUrl;
26389
+ const blobUrl = URL.createObjectURL(blob);
26390
+ blobUrlRef.current = blobUrl;
26391
+ video.src = blobUrl;
26951
26392
  }
26952
- } else {
26393
+ } else if (Hls3__default.default.isSupported()) {
26953
26394
  const blob = new Blob([src], { type: "application/vnd.apple.mpegurl" });
26954
- blobUrl = URL.createObjectURL(blob);
26955
- videoSrc = blobUrl;
26956
- console.log(`[VideoPlayer] Non-Safari browser (${browserName}) - using blob URL for source update`);
26395
+ const blobUrl = URL.createObjectURL(blob);
26396
+ blobUrlRef.current = blobUrl;
26397
+ console.log(`[HlsVideoPlayer] Non-Safari browser (${browserName}) - using HLS.js with blob URL`);
26398
+ const hls = new Hls3__default.default(mergedHlsConfig);
26399
+ hlsRef.current = hls;
26400
+ hls.on(Hls3.Events.MANIFEST_PARSED, () => {
26401
+ console.log("[HlsVideoPlayer] Manifest parsed, ready to play");
26402
+ setIsReady(true);
26403
+ eventCallbacksRef.current.onReady?.(player);
26404
+ });
26405
+ hls.on(Hls3.Events.ERROR, (event, data) => {
26406
+ console.error("[HlsVideoPlayer] HLS.js error:", data);
26407
+ if (data.fatal) {
26408
+ let errorInfo;
26409
+ switch (data.type) {
26410
+ case Hls3.ErrorTypes.NETWORK_ERROR:
26411
+ errorInfo = ERROR_MAPPING.networkError;
26412
+ console.log("[HlsVideoPlayer] Attempting to recover from network error");
26413
+ hls.startLoad();
26414
+ break;
26415
+ case Hls3.ErrorTypes.MEDIA_ERROR:
26416
+ errorInfo = ERROR_MAPPING.mediaError;
26417
+ console.log("[HlsVideoPlayer] Attempting to recover from media error");
26418
+ hls.recoverMediaError();
26419
+ break;
26420
+ case Hls3.ErrorTypes.MUX_ERROR:
26421
+ errorInfo = ERROR_MAPPING.muxError;
26422
+ break;
26423
+ default:
26424
+ errorInfo = ERROR_MAPPING.otherError;
26425
+ break;
26426
+ }
26427
+ errorInfo.details = data.details;
26428
+ eventCallbacksRef.current.onError?.(player, errorInfo);
26429
+ }
26430
+ });
26431
+ hls.on(Hls3.Events.FRAG_LOADING, () => {
26432
+ setIsLoading(true);
26433
+ eventCallbacksRef.current.onLoadingChange?.(true);
26434
+ });
26435
+ hls.on(Hls3.Events.FRAG_LOADED, () => {
26436
+ setIsLoading(false);
26437
+ eventCallbacksRef.current.onLoadingChange?.(false);
26438
+ });
26439
+ hls.loadSource(blobUrl);
26440
+ hls.attachMedia(video);
26441
+ } else {
26442
+ console.error("[HlsVideoPlayer] HLS.js not supported and not Safari - cannot play HLS content");
26443
+ const errorInfo = ERROR_MAPPING.otherError;
26444
+ onError?.(player, errorInfo);
26957
26445
  }
26958
- }
26959
- playerRef.current.src({
26960
- src: videoSrc,
26961
- type: isHLS ? "application/x-mpegURL" : "video/mp4"
26962
- });
26963
- return () => {
26964
- if (blobUrl) {
26965
- URL.revokeObjectURL(blobUrl);
26446
+ } else {
26447
+ if (Hls3__default.default.isSupported() && !isSafari()) {
26448
+ const hls = new Hls3__default.default(mergedHlsConfig);
26449
+ hlsRef.current = hls;
26450
+ hls.on(Hls3.Events.MANIFEST_PARSED, () => {
26451
+ setIsReady(true);
26452
+ eventCallbacksRef.current.onReady?.(player);
26453
+ });
26454
+ hls.on(Hls3.Events.ERROR, (event, data) => {
26455
+ console.error("[HlsVideoPlayer] HLS.js error:", data);
26456
+ if (data.fatal) {
26457
+ let errorInfo;
26458
+ switch (data.type) {
26459
+ case Hls3.ErrorTypes.NETWORK_ERROR:
26460
+ errorInfo = ERROR_MAPPING.networkError;
26461
+ hls.startLoad();
26462
+ break;
26463
+ case Hls3.ErrorTypes.MEDIA_ERROR:
26464
+ errorInfo = ERROR_MAPPING.mediaError;
26465
+ hls.recoverMediaError();
26466
+ break;
26467
+ default:
26468
+ errorInfo = ERROR_MAPPING.otherError;
26469
+ break;
26470
+ }
26471
+ errorInfo.details = data.details;
26472
+ eventCallbacksRef.current.onError?.(player, errorInfo);
26473
+ }
26474
+ });
26475
+ hls.loadSource(src);
26476
+ hls.attachMedia(video);
26477
+ } else {
26478
+ video.src = src;
26966
26479
  }
26967
- };
26480
+ }
26481
+ } else {
26482
+ video.src = src;
26968
26483
  }
26969
- }, [src]);
26484
+ const handleCanPlay = () => {
26485
+ if (!hlsRef.current) {
26486
+ setIsReady(true);
26487
+ onReady?.(player);
26488
+ }
26489
+ };
26490
+ const handlePlay = () => eventCallbacksRef.current.onPlay?.(player);
26491
+ const handlePause = () => eventCallbacksRef.current.onPause?.(player);
26492
+ const handlePlaying = () => {
26493
+ setIsLoading(false);
26494
+ eventCallbacksRef.current.onLoadingChange?.(false);
26495
+ eventCallbacksRef.current.onPlaying?.(player);
26496
+ };
26497
+ const handleTimeUpdate = () => {
26498
+ const currentTime2 = video.currentTime || 0;
26499
+ eventCallbacksRef.current.onTimeUpdate?.(player, currentTime2);
26500
+ };
26501
+ const handleDurationChange = () => {
26502
+ const duration2 = video.duration || 0;
26503
+ eventCallbacksRef.current.onDurationChange?.(player, duration2);
26504
+ };
26505
+ const handleEnded = () => eventCallbacksRef.current.onEnded?.(player);
26506
+ const handleLoadStart = () => {
26507
+ setIsLoading(true);
26508
+ eventCallbacksRef.current.onLoadingChange?.(true);
26509
+ eventCallbacksRef.current.onLoadStart?.(player);
26510
+ };
26511
+ const handleLoadedMetadata = () => eventCallbacksRef.current.onLoadedMetadata?.(player);
26512
+ const handleLoadedData = () => {
26513
+ setIsLoading(false);
26514
+ eventCallbacksRef.current.onLoadingChange?.(false);
26515
+ eventCallbacksRef.current.onLoadedData?.(player);
26516
+ };
26517
+ const handleWaiting = () => {
26518
+ setIsLoading(true);
26519
+ eventCallbacksRef.current.onLoadingChange?.(true);
26520
+ };
26521
+ const handleSeeking = () => eventCallbacksRef.current.onSeeking?.(player);
26522
+ const handleSeeked = () => eventCallbacksRef.current.onSeeked?.(player);
26523
+ const handleError = () => {
26524
+ const error = video.error;
26525
+ if (error) {
26526
+ const errorInfo = {
26527
+ code: error.code,
26528
+ type: error.code <= 2 ? "recoverable" : "non_recoverable",
26529
+ message: error.message || "Unknown error occurred",
26530
+ canRetry: error.code <= 2
26531
+ };
26532
+ eventCallbacksRef.current.onError?.(player, errorInfo);
26533
+ }
26534
+ };
26535
+ video.addEventListener("canplay", handleCanPlay);
26536
+ video.addEventListener("play", handlePlay);
26537
+ video.addEventListener("pause", handlePause);
26538
+ video.addEventListener("playing", handlePlaying);
26539
+ video.addEventListener("timeupdate", handleTimeUpdate);
26540
+ video.addEventListener("durationchange", handleDurationChange);
26541
+ video.addEventListener("ended", handleEnded);
26542
+ video.addEventListener("loadstart", handleLoadStart);
26543
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
26544
+ video.addEventListener("loadeddata", handleLoadedData);
26545
+ video.addEventListener("waiting", handleWaiting);
26546
+ video.addEventListener("seeking", handleSeeking);
26547
+ video.addEventListener("seeked", handleSeeked);
26548
+ video.addEventListener("error", handleError);
26549
+ return () => {
26550
+ video.removeEventListener("canplay", handleCanPlay);
26551
+ video.removeEventListener("play", handlePlay);
26552
+ video.removeEventListener("pause", handlePause);
26553
+ video.removeEventListener("playing", handlePlaying);
26554
+ video.removeEventListener("timeupdate", handleTimeUpdate);
26555
+ video.removeEventListener("durationchange", handleDurationChange);
26556
+ video.removeEventListener("ended", handleEnded);
26557
+ video.removeEventListener("loadstart", handleLoadStart);
26558
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
26559
+ video.removeEventListener("loadeddata", handleLoadedData);
26560
+ video.removeEventListener("waiting", handleWaiting);
26561
+ video.removeEventListener("seeking", handleSeeking);
26562
+ video.removeEventListener("seeked", handleSeeked);
26563
+ video.removeEventListener("error", handleError);
26564
+ };
26565
+ }, [
26566
+ src,
26567
+ cleanupBlobUrl,
26568
+ playerLikeObject,
26569
+ configVersion
26570
+ ]);
26970
26571
  React23.useEffect(() => {
26971
- initializePlayer();
26572
+ const cleanup = initializePlayer();
26972
26573
  return () => {
26973
- if (playerRef.current) {
26974
- const blobUrl = playerRef.current._blobUrl;
26975
- if (blobUrl) {
26976
- URL.revokeObjectURL(blobUrl);
26977
- }
26978
- playerRef.current.dispose();
26979
- playerRef.current = null;
26980
- setIsReady(false);
26574
+ cleanup?.();
26575
+ if (hlsRef.current) {
26576
+ hlsRef.current.destroy();
26577
+ hlsRef.current = null;
26981
26578
  }
26579
+ cleanupBlobUrl();
26580
+ setIsReady(false);
26982
26581
  };
26983
- }, []);
26582
+ }, [src, initializePlayer, cleanupBlobUrl]);
26583
+ React23.useEffect(() => {
26584
+ if (videoRef.current) {
26585
+ if (autoplay) {
26586
+ videoRef.current.play().catch((err) => {
26587
+ console.warn("[HlsVideoPlayer] Autoplay failed:", err);
26588
+ });
26589
+ }
26590
+ }
26591
+ }, [autoplay]);
26984
26592
  const play = React23.useCallback(() => {
26985
- return playerRef.current?.play();
26593
+ return videoRef.current?.play();
26986
26594
  }, []);
26987
26595
  const pause = React23.useCallback(() => {
26988
- playerRef.current?.pause();
26596
+ videoRef.current?.pause();
26989
26597
  }, []);
26990
26598
  const currentTime = React23.useCallback((time2) => {
26991
- if (time2 !== void 0) {
26992
- playerRef.current?.currentTime(time2);
26599
+ if (time2 !== void 0 && videoRef.current) {
26600
+ videoRef.current.currentTime = time2;
26993
26601
  return time2;
26994
26602
  }
26995
- return playerRef.current?.currentTime() || 0;
26603
+ return videoRef.current?.currentTime || 0;
26996
26604
  }, []);
26997
26605
  const duration = React23.useCallback(() => {
26998
- return playerRef.current?.duration() || 0;
26606
+ return videoRef.current?.duration || 0;
26999
26607
  }, []);
27000
26608
  const paused = React23.useCallback(() => {
27001
- return playerRef.current?.paused() ?? true;
26609
+ return videoRef.current?.paused ?? true;
27002
26610
  }, []);
27003
26611
  const mute = React23.useCallback((isMuted) => {
27004
- if (isMuted !== void 0) {
27005
- playerRef.current?.muted(isMuted);
26612
+ if (isMuted !== void 0 && videoRef.current) {
26613
+ videoRef.current.muted = isMuted;
27006
26614
  return isMuted;
27007
26615
  }
27008
- return playerRef.current?.muted() ?? false;
26616
+ return videoRef.current?.muted ?? false;
27009
26617
  }, []);
27010
26618
  const volume = React23.useCallback((level) => {
27011
- if (level !== void 0) {
27012
- playerRef.current?.volume(level);
26619
+ if (level !== void 0 && videoRef.current) {
26620
+ videoRef.current.volume = level;
27013
26621
  return level;
27014
26622
  }
27015
- return playerRef.current?.volume() ?? 1;
26623
+ return videoRef.current?.volume ?? 1;
27016
26624
  }, []);
27017
- const dispose = React23.useCallback(() => {
27018
- if (playerRef.current) {
27019
- playerRef.current.dispose();
27020
- playerRef.current = null;
27021
- setIsReady(false);
26625
+ const playbackRate = React23.useCallback((rate) => {
26626
+ if (rate !== void 0 && videoRef.current) {
26627
+ videoRef.current.playbackRate = rate;
26628
+ return rate;
27022
26629
  }
26630
+ return videoRef.current?.playbackRate ?? 1;
27023
26631
  }, []);
27024
- React23__namespace.default.useImperativeHandle(ref, () => ({
27025
- player: playerRef.current,
26632
+ React23.useImperativeHandle(ref, () => ({
26633
+ hls: hlsRef.current,
26634
+ video: videoRef.current,
27026
26635
  play,
27027
26636
  pause,
27028
26637
  currentTime,
@@ -27030,13 +26639,15 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
27030
26639
  paused,
27031
26640
  mute,
27032
26641
  volume,
26642
+ playbackRate,
27033
26643
  dispose,
27034
- isReady
27035
- }));
26644
+ isReady,
26645
+ // For backward compatibility with Video.js API
26646
+ player: playerLikeObject()
26647
+ }), [play, pause, currentTime, duration, paused, mute, volume, playbackRate, dispose, isReady, playerLikeObject]);
27036
26648
  const handleClickWithIndicator = React23.useCallback(() => {
27037
- if (!onClick || !playerRef.current) return;
27038
- const player = playerRef.current;
27039
- const willBePlaying = player.paused();
26649
+ if (!onClick || !videoRef.current) return;
26650
+ const willBePlaying = videoRef.current.paused;
27040
26651
  setIndicatorIsPlaying(willBePlaying);
27041
26652
  setShowIndicator(false);
27042
26653
  setTimeout(() => {
@@ -27045,17 +26656,30 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
27045
26656
  }, 0);
27046
26657
  onClick();
27047
26658
  }, [onClick]);
27048
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `video-player-wrapper ${className}`, style: { position: "relative", width: "100%", height: "100%" }, children: [
26659
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `hls-video-player-wrapper ${className}`, style: { position: "relative", width: "100%", height: "100%" }, children: [
27049
26660
  /* @__PURE__ */ jsxRuntime.jsx(
27050
26661
  "div",
27051
26662
  {
27052
- className: "video-player-container",
27053
- ref: videoRef,
27054
- "data-vjs-player": true
26663
+ className: "hls-video-player-container",
26664
+ ref: videoContainerRef,
26665
+ children: /* @__PURE__ */ jsxRuntime.jsx(
26666
+ "video",
26667
+ {
26668
+ ref: videoRef,
26669
+ className: "hls-video-element",
26670
+ poster,
26671
+ controls,
26672
+ loop,
26673
+ muted,
26674
+ playsInline,
26675
+ autoPlay: autoplay,
26676
+ preload: "metadata"
26677
+ }
26678
+ )
27055
26679
  }
27056
26680
  ),
27057
- isLoading && !externalLoadingControl && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "video-player-loading", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
27058
- onClick && /* @__PURE__ */ jsxRuntime.jsx(
26681
+ isLoading && !externalLoadingControl && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hls-video-player-loading", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
26682
+ onClick && !controls && /* @__PURE__ */ jsxRuntime.jsx(
27059
26683
  "div",
27060
26684
  {
27061
26685
  onClick: handleClickWithIndicator,
@@ -27071,7 +26695,7 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
27071
26695
  "aria-label": "Click to play/pause"
27072
26696
  }
27073
26697
  ),
27074
- onClick && /* @__PURE__ */ jsxRuntime.jsx(
26698
+ onClick && !controls && /* @__PURE__ */ jsxRuntime.jsx(
27075
26699
  PlayPauseIndicator,
27076
26700
  {
27077
26701
  show: showIndicator,
@@ -27081,13 +26705,25 @@ var VideoPlayer = React23__namespace.default.forwardRef(({
27081
26705
  )
27082
26706
  ] });
27083
26707
  });
27084
- VideoPlayer.displayName = "VideoPlayer";
27085
- var CroppedVideoPlayer = React23.forwardRef(({
26708
+ HlsVideoPlayer.displayName = "HlsVideoPlayer";
26709
+ var VideoPlayer = HlsVideoPlayer;
26710
+ var CroppedHlsVideoPlayer = React23.forwardRef(({
27086
26711
  crop,
27087
26712
  debug = false,
27088
26713
  onClick,
27089
26714
  ...videoProps
27090
26715
  }, ref) => {
26716
+ const {
26717
+ onReady: onReadyProp,
26718
+ onPlay: onPlayProp,
26719
+ onPause: onPauseProp,
26720
+ onEnded: onEndedProp,
26721
+ onSeeking: onSeekingProp,
26722
+ onSeeked: onSeekedProp,
26723
+ onLoadedMetadata: onLoadedMetadataProp,
26724
+ className: inheritedClassName = ""
26725
+ } = videoProps;
26726
+ const videoSrc = videoProps.src;
27091
26727
  const videoContainerRef = React23.useRef(null);
27092
26728
  const hiddenVideoRef = React23.useRef(null);
27093
26729
  const canvasRef = React23.useRef(null);
@@ -27106,8 +26742,11 @@ var CroppedVideoPlayer = React23.forwardRef(({
27106
26742
  }
27107
26743
  }, []);
27108
26744
  React23.useImperativeHandle(ref, () => ({
27109
- get player() {
27110
- return hiddenVideoRef.current?.player || null;
26745
+ get hls() {
26746
+ return hiddenVideoRef.current?.hls || null;
26747
+ },
26748
+ get video() {
26749
+ return hiddenVideoRef.current?.video || null;
27111
26750
  },
27112
26751
  play: () => hiddenVideoRef.current?.play() || void 0,
27113
26752
  pause: () => hiddenVideoRef.current?.pause(),
@@ -27121,12 +26760,36 @@ var CroppedVideoPlayer = React23.forwardRef(({
27121
26760
  paused: () => hiddenVideoRef.current?.paused() || true,
27122
26761
  mute: (isMuted) => hiddenVideoRef.current?.mute(isMuted) || false,
27123
26762
  volume: (level) => hiddenVideoRef.current?.volume(level) || 0,
26763
+ playbackRate: (rate) => hiddenVideoRef.current?.playbackRate(rate) || 1,
27124
26764
  dispose: () => {
27125
26765
  hiddenVideoRef.current?.dispose();
27126
26766
  stopCanvasRendering();
27127
26767
  },
27128
26768
  get isReady() {
27129
26769
  return hiddenVideoRef.current?.isReady || false;
26770
+ },
26771
+ // For backward compatibility with Video.js API
26772
+ get player() {
26773
+ const video = hiddenVideoRef.current?.video;
26774
+ if (!video) return null;
26775
+ return {
26776
+ el: () => video,
26777
+ currentTime: () => video.currentTime || 0,
26778
+ duration: () => video.duration || 0,
26779
+ paused: () => video.paused ?? true,
26780
+ play: () => video.play(),
26781
+ pause: () => video.pause(),
26782
+ muted: (val) => {
26783
+ if (val !== void 0) video.muted = val;
26784
+ return video.muted;
26785
+ },
26786
+ volume: (val) => {
26787
+ if (val !== void 0) video.volume = val;
26788
+ return video.volume;
26789
+ },
26790
+ error: () => null,
26791
+ dispose: () => hiddenVideoRef.current?.dispose()
26792
+ };
27130
26793
  }
27131
26794
  }), [stopCanvasRendering]);
27132
26795
  const calculateCanvasDimensions = React23.useCallback(() => {
@@ -27186,24 +26849,24 @@ var CroppedVideoPlayer = React23.forwardRef(({
27186
26849
  animationFrameRef.current = requestAnimationFrame(renderFrameToCanvas);
27187
26850
  }, [crop]);
27188
26851
  const handleVideoReady = React23.useCallback((player) => {
27189
- console.log("[CroppedVideoPlayer] Video player ready");
27190
- const videoEl = player.el().querySelector("video");
26852
+ console.log("[CroppedHlsVideoPlayer] Video player ready");
26853
+ const videoEl = hiddenVideoRef.current?.video;
27191
26854
  if (videoEl) {
27192
26855
  videoElementRef.current = videoEl;
27193
26856
  setIsVideoReady(true);
27194
26857
  }
27195
- videoProps.onReady?.(player);
27196
- }, [videoProps]);
26858
+ onReadyProp?.(player);
26859
+ }, [onReadyProp]);
27197
26860
  const handleVideoPlay = React23.useCallback((player) => {
27198
- console.log("[CroppedVideoPlayer] Video playing, starting canvas rendering");
26861
+ console.log("[CroppedHlsVideoPlayer] Video playing, starting canvas rendering");
27199
26862
  if (crop && canvasRef.current) {
27200
26863
  setIsProcessing(true);
27201
26864
  renderFrameToCanvas();
27202
26865
  }
27203
- videoProps.onPlay?.(player);
27204
- }, [crop, renderFrameToCanvas, videoProps]);
26866
+ onPlayProp?.(player);
26867
+ }, [crop, renderFrameToCanvas, onPlayProp]);
27205
26868
  const handleVideoPause = React23.useCallback((player) => {
27206
- console.log("[CroppedVideoPlayer] Video paused, stopping canvas rendering and CLEARING canvas");
26869
+ console.log("[CroppedHlsVideoPlayer] Video paused, stopping canvas rendering and CLEARING canvas");
27207
26870
  stopCanvasRendering();
27208
26871
  setIsProcessing(false);
27209
26872
  if (canvasRef.current) {
@@ -27214,10 +26877,10 @@ var CroppedVideoPlayer = React23.forwardRef(({
27214
26877
  ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
27215
26878
  }
27216
26879
  }
27217
- videoProps.onPause?.(player);
27218
- }, [stopCanvasRendering, videoProps]);
26880
+ onPauseProp?.(player);
26881
+ }, [stopCanvasRendering, onPauseProp]);
27219
26882
  const handleVideoEnded = React23.useCallback((player) => {
27220
- console.log("[CroppedVideoPlayer] Video ended, CLEARING canvas");
26883
+ console.log("[CroppedHlsVideoPlayer] Video ended, CLEARING canvas");
27221
26884
  stopCanvasRendering();
27222
26885
  setIsProcessing(false);
27223
26886
  if (canvasRef.current) {
@@ -27228,27 +26891,27 @@ var CroppedVideoPlayer = React23.forwardRef(({
27228
26891
  ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height);
27229
26892
  }
27230
26893
  }
27231
- videoProps.onEnded?.(player);
27232
- }, [stopCanvasRendering, videoProps]);
26894
+ onEndedProp?.(player);
26895
+ }, [stopCanvasRendering, onEndedProp]);
27233
26896
  const handleSeeking = React23.useCallback((player) => {
27234
- console.log("[CroppedVideoPlayer] Video seeking");
27235
- if (crop && !player.paused()) {
26897
+ console.log("[CroppedHlsVideoPlayer] Video seeking");
26898
+ if (crop && !videoElementRef.current?.paused) {
27236
26899
  renderFrameToCanvas();
27237
26900
  }
27238
- videoProps.onSeeking?.(player);
27239
- }, [crop, renderFrameToCanvas, videoProps]);
26901
+ onSeekingProp?.(player);
26902
+ }, [crop, renderFrameToCanvas, onSeekingProp]);
27240
26903
  const handleSeeked = React23.useCallback((player) => {
27241
- console.log("[CroppedVideoPlayer] Video seeked");
27242
- if (crop && !player.paused()) {
26904
+ console.log("[CroppedHlsVideoPlayer] Video seeked");
26905
+ if (crop && !videoElementRef.current?.paused) {
27243
26906
  renderFrameToCanvas();
27244
26907
  }
27245
- videoProps.onSeeked?.(player);
27246
- }, [crop, renderFrameToCanvas, videoProps]);
26908
+ onSeekedProp?.(player);
26909
+ }, [crop, renderFrameToCanvas, onSeekedProp]);
27247
26910
  const handleLoadedMetadata = React23.useCallback((player) => {
27248
- console.log("[CroppedVideoPlayer] Video metadata loaded");
26911
+ console.log("[CroppedHlsVideoPlayer] Video metadata loaded");
27249
26912
  calculateCanvasDimensions();
27250
- videoProps.onLoadedMetadata?.(player);
27251
- }, [calculateCanvasDimensions, videoProps]);
26913
+ onLoadedMetadataProp?.(player);
26914
+ }, [calculateCanvasDimensions, onLoadedMetadataProp]);
27252
26915
  React23.useEffect(() => {
27253
26916
  calculateCanvasDimensions();
27254
26917
  const handleResize = () => {
@@ -27264,25 +26927,25 @@ var CroppedVideoPlayer = React23.forwardRef(({
27264
26927
  const canvas = canvasRef.current;
27265
26928
  const ctx = canvas.getContext("2d");
27266
26929
  if (ctx) {
27267
- console.log("[CroppedVideoPlayer] Source changing - CLEARING CANVAS IMMEDIATELY");
26930
+ console.log("[CroppedHlsVideoPlayer] Source changing - CLEARING CANVAS IMMEDIATELY");
27268
26931
  ctx.clearRect(0, 0, canvas.width, canvas.height);
27269
26932
  ctx.fillStyle = "black";
27270
26933
  ctx.fillRect(0, 0, canvas.width, canvas.height);
27271
26934
  }
27272
26935
  }
27273
- }, [videoProps.src, crop]);
26936
+ }, [videoSrc, crop]);
27274
26937
  React23.useEffect(() => {
27275
26938
  return () => {
27276
26939
  stopCanvasRendering();
27277
26940
  };
27278
26941
  }, [stopCanvasRendering]);
27279
26942
  if (!crop) {
27280
- return /* @__PURE__ */ jsxRuntime.jsx(VideoPlayer, { ref, ...videoProps, onClick });
26943
+ return /* @__PURE__ */ jsxRuntime.jsx(HlsVideoPlayer, { ref, ...videoProps, onClick });
27281
26944
  }
27282
26945
  const handleClickWithIndicator = () => {
27283
- if (!onClick || !hiddenVideoRef.current?.player) return;
27284
- const player = hiddenVideoRef.current.player;
27285
- const willBePlaying = player.paused();
26946
+ if (!onClick || !hiddenVideoRef.current?.video) return;
26947
+ const video = hiddenVideoRef.current.video;
26948
+ const willBePlaying = video.paused;
27286
26949
  setIndicatorIsPlaying(willBePlaying);
27287
26950
  setShowIndicator(false);
27288
26951
  setTimeout(() => {
@@ -27295,11 +26958,11 @@ var CroppedVideoPlayer = React23.forwardRef(({
27295
26958
  "div",
27296
26959
  {
27297
26960
  ref: videoContainerRef,
27298
- className: `relative w-full h-full flex items-center justify-center bg-black ${onClick ? "cursor-pointer" : ""} ${videoProps.className || ""}`,
26961
+ className: `relative w-full h-full flex items-center justify-center bg-black ${onClick ? "cursor-pointer" : ""} ${inheritedClassName}`,
27299
26962
  onClick: handleClickWithIndicator,
27300
26963
  children: [
27301
26964
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
27302
- VideoPlayer,
26965
+ HlsVideoPlayer,
27303
26966
  {
27304
26967
  ref: hiddenVideoRef,
27305
26968
  ...videoProps,
@@ -27366,7 +27029,539 @@ var CroppedVideoPlayer = React23.forwardRef(({
27366
27029
  }
27367
27030
  );
27368
27031
  });
27369
- CroppedVideoPlayer.displayName = "CroppedVideoPlayer";
27032
+ CroppedHlsVideoPlayer.displayName = "CroppedHlsVideoPlayer";
27033
+ var CroppedVideoPlayer = CroppedHlsVideoPlayer;
27034
+ var getSupabaseClient2 = () => {
27035
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
27036
+ const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
27037
+ if (!url || !key) {
27038
+ throw new Error("Supabase configuration missing");
27039
+ }
27040
+ return supabaseJs.createClient(url, key);
27041
+ };
27042
+ var getAuthToken3 = async () => {
27043
+ try {
27044
+ const supabase = getSupabaseClient2();
27045
+ const { data: { session } } = await supabase.auth.getSession();
27046
+ return session?.access_token || null;
27047
+ } catch (error) {
27048
+ console.error("[useWorkspaceCrop] Error getting auth token:", error);
27049
+ return null;
27050
+ }
27051
+ };
27052
+ function useWorkspaceCrop(workspaceId) {
27053
+ const [crop, setCrop] = React23.useState(null);
27054
+ const [isLoading, setIsLoading] = React23.useState(true);
27055
+ const [error, setError] = React23.useState(null);
27056
+ React23.useEffect(() => {
27057
+ if (!workspaceId) {
27058
+ setIsLoading(false);
27059
+ return;
27060
+ }
27061
+ const fetchCrop = async () => {
27062
+ setIsLoading(true);
27063
+ setError(null);
27064
+ try {
27065
+ const token = await getAuthToken3();
27066
+ if (!token) {
27067
+ throw new Error("Authentication required");
27068
+ }
27069
+ const response = await fetch("/api/clips/supabase", {
27070
+ method: "POST",
27071
+ headers: {
27072
+ "Content-Type": "application/json",
27073
+ "Authorization": `Bearer ${token}`
27074
+ },
27075
+ body: JSON.stringify({
27076
+ action: "crop",
27077
+ workspaceId
27078
+ })
27079
+ });
27080
+ if (!response.ok) {
27081
+ throw new Error(`Failed to fetch crop: ${response.statusText}`);
27082
+ }
27083
+ const data = await response.json();
27084
+ console.log(`[useWorkspaceCrop] Fetched crop for workspace ${workspaceId}:`, data.crop);
27085
+ setCrop(data.crop);
27086
+ } catch (err) {
27087
+ console.error("[useWorkspaceCrop] Error fetching crop:", err);
27088
+ setError(err instanceof Error ? err.message : "Failed to fetch crop configuration");
27089
+ setCrop(null);
27090
+ } finally {
27091
+ setIsLoading(false);
27092
+ }
27093
+ };
27094
+ fetchCrop();
27095
+ }, [workspaceId]);
27096
+ return { crop, isLoading, error };
27097
+ }
27098
+ function Skeleton({ className, ...props }) {
27099
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("animate-pulse rounded-md bg-muted", className), ...props });
27100
+ }
27101
+ var Select = SelectPrimitive__namespace.Root;
27102
+ var SelectGroup = SelectPrimitive__namespace.Group;
27103
+ var SelectValue = SelectPrimitive__namespace.Value;
27104
+ var SelectTrigger = React23__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
27105
+ SelectPrimitive__namespace.Trigger,
27106
+ {
27107
+ ref,
27108
+ className: cn(
27109
+ "flex h-11 sm:h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 touch-manipulation",
27110
+ className
27111
+ ),
27112
+ ...props,
27113
+ children: [
27114
+ children,
27115
+ /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Icon, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 opacity-50" }) })
27116
+ ]
27117
+ }
27118
+ ));
27119
+ SelectTrigger.displayName = SelectPrimitive__namespace.Trigger.displayName;
27120
+ var SelectScrollUpButton = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
27121
+ SelectPrimitive__namespace.ScrollUpButton,
27122
+ {
27123
+ ref,
27124
+ className: cn("flex cursor-default items-center justify-center py-1", className),
27125
+ ...props,
27126
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-4 w-4" })
27127
+ }
27128
+ ));
27129
+ SelectScrollUpButton.displayName = SelectPrimitive__namespace.ScrollUpButton.displayName;
27130
+ var SelectScrollDownButton = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
27131
+ SelectPrimitive__namespace.ScrollDownButton,
27132
+ {
27133
+ ref,
27134
+ className: cn("flex cursor-default items-center justify-center py-1", className),
27135
+ ...props,
27136
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" })
27137
+ }
27138
+ ));
27139
+ SelectScrollDownButton.displayName = SelectPrimitive__namespace.ScrollDownButton.displayName;
27140
+ var SelectContent = React23__namespace.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
27141
+ SelectPrimitive__namespace.Content,
27142
+ {
27143
+ ref,
27144
+ className: cn(
27145
+ "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
27146
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
27147
+ className
27148
+ ),
27149
+ position,
27150
+ ...props,
27151
+ children: [
27152
+ /* @__PURE__ */ jsxRuntime.jsx(SelectScrollUpButton, {}),
27153
+ /* @__PURE__ */ jsxRuntime.jsx(
27154
+ SelectPrimitive__namespace.Viewport,
27155
+ {
27156
+ className: cn(
27157
+ "p-1",
27158
+ position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
27159
+ ),
27160
+ children
27161
+ }
27162
+ ),
27163
+ /* @__PURE__ */ jsxRuntime.jsx(SelectScrollDownButton, {})
27164
+ ]
27165
+ }
27166
+ ) }));
27167
+ SelectContent.displayName = SelectPrimitive__namespace.Content.displayName;
27168
+ var SelectLabel = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
27169
+ SelectPrimitive__namespace.Label,
27170
+ {
27171
+ ref,
27172
+ className: cn("px-2 py-1.5 text-sm font-semibold", className),
27173
+ ...props
27174
+ }
27175
+ ));
27176
+ SelectLabel.displayName = SelectPrimitive__namespace.Label.displayName;
27177
+ var SelectItem = React23__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
27178
+ SelectPrimitive__namespace.Item,
27179
+ {
27180
+ ref,
27181
+ className: cn(
27182
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
27183
+ className
27184
+ ),
27185
+ ...props,
27186
+ children: [
27187
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemIndicator, { children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }) }),
27188
+ /* @__PURE__ */ jsxRuntime.jsx(SelectPrimitive__namespace.ItemText, { children })
27189
+ ]
27190
+ }
27191
+ ));
27192
+ SelectItem.displayName = SelectPrimitive__namespace.Item.displayName;
27193
+ var SelectSeparator = React23__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
27194
+ SelectPrimitive__namespace.Separator,
27195
+ {
27196
+ ref,
27197
+ className: cn("-mx-1 my-1 h-px bg-muted", className),
27198
+ ...props
27199
+ }
27200
+ ));
27201
+ SelectSeparator.displayName = SelectPrimitive__namespace.Separator.displayName;
27202
+ var LoadingOverlay = ({
27203
+ isVisible,
27204
+ message = "Loading...",
27205
+ className
27206
+ }) => {
27207
+ if (!isVisible) return null;
27208
+ return /* @__PURE__ */ jsxRuntime.jsx(
27209
+ motion.div,
27210
+ {
27211
+ initial: { opacity: 0 },
27212
+ animate: { opacity: 1 },
27213
+ exit: { opacity: 0 },
27214
+ transition: { duration: 0.2 },
27215
+ className: `fixed inset-0 z-[100] flex items-center justify-center bg-black/30 backdrop-blur-sm ${className || ""}`,
27216
+ "aria-modal": "true",
27217
+ role: "dialog",
27218
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center space-y-3 rounded-lg bg-white p-8 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message }) })
27219
+ }
27220
+ );
27221
+ };
27222
+ var LoadingOverlay_default = LoadingOverlay;
27223
+ var TimeDisplay = ({ className, variant = "default" }) => {
27224
+ const { dateTimeConfig } = useDashboardConfig();
27225
+ const [time2, setTime] = React23.useState("");
27226
+ const dbTimezone = useAppTimezone();
27227
+ const timezoneToDisplay = dbTimezone || dateTimeConfig?.defaultTimezone || "UTC";
27228
+ const localeToUse = dateTimeConfig?.defaultLocale || "en-US";
27229
+ const timeSuffix = "";
27230
+ React23.useEffect(() => {
27231
+ const updateTime = () => {
27232
+ const now2 = /* @__PURE__ */ new Date();
27233
+ const effectiveFormatOptions = {
27234
+ hour: "2-digit",
27235
+ minute: "2-digit",
27236
+ second: "2-digit",
27237
+ hour12: true,
27238
+ timeZone: timezoneToDisplay,
27239
+ ...dateTimeConfig?.timeFormatOptions || {}
27240
+ // Allow override from config
27241
+ };
27242
+ try {
27243
+ setTime(new Intl.DateTimeFormat(localeToUse, effectiveFormatOptions).format(now2));
27244
+ } catch (e) {
27245
+ console.error("Error formatting time:", e);
27246
+ setTime("Error");
27247
+ }
27248
+ };
27249
+ updateTime();
27250
+ const interval = setInterval(updateTime, 1e3);
27251
+ return () => clearInterval(interval);
27252
+ }, [timezoneToDisplay, dateTimeConfig?.timeFormatOptions, localeToUse]);
27253
+ if (!time2) return null;
27254
+ if (variant === "minimal") {
27255
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: className || "", children: [
27256
+ time2,
27257
+ " ",
27258
+ timeSuffix
27259
+ ] });
27260
+ }
27261
+ return /* @__PURE__ */ jsxRuntime.jsxs(
27262
+ motion.div,
27263
+ {
27264
+ initial: { opacity: 0, y: -5 },
27265
+ animate: { opacity: 1, y: 0 },
27266
+ transition: { duration: 0.3 },
27267
+ className: `flex items-center space-x-1.5 bg-white/60 backdrop-blur-sm px-2 py-0.5 rounded-md shadow-xs ${className || ""}`,
27268
+ children: [
27269
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-3 w-3 text-[var(--primary-DEFAULT)]", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z", clipRule: "evenodd" }) }),
27270
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs sm:text-[11px] font-medium text-gray-800 tabular-nums tracking-tight", children: [
27271
+ time2,
27272
+ " ",
27273
+ timeSuffix
27274
+ ] })
27275
+ ]
27276
+ }
27277
+ );
27278
+ };
27279
+ var TimeDisplay_default = TimeDisplay;
27280
+ var DateDisplay = ({ className, variant = "default" }) => {
27281
+ const { dateTimeConfig } = useDashboardConfig();
27282
+ const [date, setDate] = React23.useState("");
27283
+ const timezoneToDisplay = dateTimeConfig?.defaultTimezone || "UTC";
27284
+ const localeToUse = dateTimeConfig?.defaultLocale || "en-US";
27285
+ React23.useEffect(() => {
27286
+ const getCurrentFormattedDate = () => {
27287
+ const now2 = /* @__PURE__ */ new Date();
27288
+ const effectiveFormatOptions = {
27289
+ weekday: "short",
27290
+ day: "numeric",
27291
+ month: "short",
27292
+ timeZone: timezoneToDisplay,
27293
+ ...dateTimeConfig?.dateFormatOptions || {}
27294
+ // Allow override from config
27295
+ };
27296
+ try {
27297
+ return new Intl.DateTimeFormat(localeToUse, effectiveFormatOptions).format(now2);
27298
+ } catch (e) {
27299
+ console.error("Error formatting date:", e);
27300
+ return "Error";
27301
+ }
27302
+ };
27303
+ const updateDate = () => {
27304
+ setDate(getCurrentFormattedDate());
27305
+ };
27306
+ updateDate();
27307
+ const interval = setInterval(() => {
27308
+ const currentDateStr = getCurrentFormattedDate();
27309
+ if (currentDateStr !== date) {
27310
+ updateDate();
27311
+ }
27312
+ }, 60 * 1e3);
27313
+ return () => clearInterval(interval);
27314
+ }, [date, timezoneToDisplay, dateTimeConfig?.dateFormatOptions, localeToUse]);
27315
+ if (!date) return null;
27316
+ if (variant === "minimal") {
27317
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: className || "", children: date });
27318
+ }
27319
+ return /* @__PURE__ */ jsxRuntime.jsxs(
27320
+ motion.div,
27321
+ {
27322
+ initial: { opacity: 0, y: -5 },
27323
+ animate: { opacity: 1, y: 0 },
27324
+ transition: { duration: 0.3 },
27325
+ className: `flex items-center space-x-1.5 bg-white/60 backdrop-blur-sm px-2 py-0.5 rounded-md shadow-xs ${className || ""}`,
27326
+ children: [
27327
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-3 w-3 text-blue-600", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", clipRule: "evenodd" }) }),
27328
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-gray-800 tracking-tight", children: date })
27329
+ ]
27330
+ }
27331
+ );
27332
+ };
27333
+ var DateDisplay_default = DateDisplay;
27334
+ var Card3 = Card2;
27335
+ var CardHeader3 = CardHeader2;
27336
+ var CardTitle3 = CardTitle2;
27337
+ var CardContent3 = CardContent2;
27338
+ var MetricCard2 = ({ title, value, unit = "", trend = null }) => {
27339
+ const getTrendColor = (trendValue) => {
27340
+ if (trendValue === null || trendValue === void 0) return "";
27341
+ return trendValue > 0 ? "text-green-500" : trendValue < 0 ? "text-red-500" : "text-gray-500";
27342
+ };
27343
+ const getTrendSymbol = (trendValue) => {
27344
+ if (trendValue === null || trendValue === void 0) return "";
27345
+ return trendValue > 0 ? "\u2191" : trendValue < 0 ? "\u2193" : "";
27346
+ };
27347
+ return /* @__PURE__ */ jsxRuntime.jsxs(Card3, { className: "bg-white", children: [
27348
+ /* @__PURE__ */ jsxRuntime.jsx(CardHeader3, { className: "pb-2", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle3, { className: "text-sm font-medium text-gray-500", children: title }) }),
27349
+ /* @__PURE__ */ jsxRuntime.jsx(CardContent3, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between", children: [
27350
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl font-semibold", children: [
27351
+ value,
27352
+ unit && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-sm text-gray-500", children: unit })
27353
+ ] }),
27354
+ trend !== null && trend !== void 0 && // Check trend for null/undefined before accessing
27355
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex items-center ${getTrendColor(trend)}`, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm", children: [
27356
+ getTrendSymbol(trend),
27357
+ " ",
27358
+ Math.abs(trend),
27359
+ "%"
27360
+ ] }) })
27361
+ ] }) })
27362
+ ] });
27363
+ };
27364
+ var MetricCard_default = MetricCard2;
27365
+ var TimePickerDropdown = ({
27366
+ value,
27367
+ onChange,
27368
+ placeholder = "Select time",
27369
+ className = "",
27370
+ disabled = false
27371
+ }) => {
27372
+ const [isOpen, setIsOpen] = React23.useState(false);
27373
+ const [searchTerm, setSearchTerm] = React23.useState("");
27374
+ const dropdownRef = React23.useRef(null);
27375
+ const inputRef = React23.useRef(null);
27376
+ const generateTimeSlots = () => {
27377
+ const slots = [];
27378
+ for (let hour = 0; hour < 24; hour++) {
27379
+ for (let minute = 0; minute < 60; minute += 15) {
27380
+ const time24 = `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
27381
+ const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
27382
+ const ampm = hour < 12 ? "AM" : "PM";
27383
+ const time12 = `${hour12}:${minute.toString().padStart(2, "0")} ${ampm}`;
27384
+ slots.push({ value: time24, label: time12 });
27385
+ }
27386
+ }
27387
+ return slots;
27388
+ };
27389
+ const timeSlots = generateTimeSlots();
27390
+ const filteredSlots = timeSlots.filter(
27391
+ (slot) => slot.label.toLowerCase().includes(searchTerm.toLowerCase())
27392
+ );
27393
+ const getDisplayValue = (value2) => {
27394
+ if (!value2) return "";
27395
+ const normalizedValue = value2.substring(0, 5);
27396
+ const slot = timeSlots.find((s) => s.value === normalizedValue);
27397
+ return slot ? slot.label : value2;
27398
+ };
27399
+ React23.useEffect(() => {
27400
+ const handleClickOutside = (event) => {
27401
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
27402
+ setIsOpen(false);
27403
+ setSearchTerm("");
27404
+ }
27405
+ };
27406
+ document.addEventListener("mousedown", handleClickOutside);
27407
+ return () => document.removeEventListener("mousedown", handleClickOutside);
27408
+ }, []);
27409
+ const handleKeyDown = (e) => {
27410
+ if (e.key === "Escape") {
27411
+ setIsOpen(false);
27412
+ setSearchTerm("");
27413
+ } else if (e.key === "Enter") {
27414
+ e.preventDefault();
27415
+ if (filteredSlots.length > 0) {
27416
+ onChange(filteredSlots[0].value);
27417
+ setIsOpen(false);
27418
+ setSearchTerm("");
27419
+ }
27420
+ }
27421
+ };
27422
+ const handleSelect = (timeValue) => {
27423
+ onChange(timeValue);
27424
+ setIsOpen(false);
27425
+ setSearchTerm("");
27426
+ };
27427
+ const handleToggle = () => {
27428
+ if (disabled) return;
27429
+ setIsOpen(!isOpen);
27430
+ if (!isOpen) {
27431
+ setTimeout(() => inputRef.current?.focus(), 100);
27432
+ }
27433
+ };
27434
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${className}`, ref: dropdownRef, children: [
27435
+ /* @__PURE__ */ jsxRuntime.jsx(
27436
+ "button",
27437
+ {
27438
+ type: "button",
27439
+ onClick: handleToggle,
27440
+ disabled,
27441
+ className: `
27442
+ w-full px-3 py-2 text-left bg-white border border-gray-300 rounded-md shadow-sm
27443
+ focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
27444
+ hover:border-gray-400 transition-colors duration-200
27445
+ ${disabled ? "bg-gray-50 cursor-not-allowed" : "cursor-pointer"}
27446
+ ${isOpen ? "ring-2 ring-blue-500 border-blue-500" : ""}
27447
+ `,
27448
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
27449
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
27450
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4 text-gray-400" }),
27451
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-sm ${value ? "text-gray-900" : "text-gray-500"}`, children: value ? getDisplayValue(value) : placeholder })
27452
+ ] }),
27453
+ /* @__PURE__ */ jsxRuntime.jsx(
27454
+ lucideReact.ChevronDown,
27455
+ {
27456
+ className: `h-4 w-4 text-gray-400 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`
27457
+ }
27458
+ )
27459
+ ] })
27460
+ }
27461
+ ),
27462
+ isOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-hidden", children: [
27463
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx(
27464
+ "input",
27465
+ {
27466
+ ref: inputRef,
27467
+ type: "text",
27468
+ placeholder: "Search time...",
27469
+ value: searchTerm,
27470
+ onChange: (e) => setSearchTerm(e.target.value),
27471
+ onKeyDown: handleKeyDown,
27472
+ className: "w-full px-3 py-2 text-sm border border-gray-200 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
27473
+ }
27474
+ ) }),
27475
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-y-auto max-h-48", children: filteredSlots.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-gray-500 text-center", children: "No times found" }) : filteredSlots.map((slot) => /* @__PURE__ */ jsxRuntime.jsx(
27476
+ "button",
27477
+ {
27478
+ type: "button",
27479
+ onClick: () => handleSelect(slot.value),
27480
+ className: `
27481
+ w-full px-3 py-2 text-left text-sm hover:bg-blue-50 hover:text-blue-600
27482
+ transition-colors duration-150 border-b border-gray-100 last:border-b-0
27483
+ ${slot.value === value ? "bg-blue-50 text-blue-600 font-medium" : "text-gray-700"}
27484
+ `,
27485
+ children: slot.label
27486
+ },
27487
+ slot.value
27488
+ )) })
27489
+ ] })
27490
+ ] });
27491
+ };
27492
+ var SilentErrorBoundary = class extends React23__namespace.default.Component {
27493
+ constructor(props) {
27494
+ super(props);
27495
+ this.handleClearAndReload = () => {
27496
+ console.log("[ErrorBoundary] User initiated reset");
27497
+ if (typeof window !== "undefined") {
27498
+ try {
27499
+ localStorage.clear();
27500
+ sessionStorage.clear();
27501
+ console.log("[ErrorBoundary] Cleared all storage");
27502
+ } catch (error) {
27503
+ console.error("[ErrorBoundary] Failed to clear storage:", error);
27504
+ }
27505
+ }
27506
+ window.location.href = "/login";
27507
+ };
27508
+ this.state = {
27509
+ hasError: false,
27510
+ errorCount: 0,
27511
+ lastError: null,
27512
+ errorInfo: null
27513
+ };
27514
+ }
27515
+ static getDerivedStateFromError(error) {
27516
+ return { hasError: true };
27517
+ }
27518
+ componentDidCatch(error, errorInfo) {
27519
+ console.error("[ErrorBoundary] Caught render error:", {
27520
+ error: error.message,
27521
+ stack: error.stack,
27522
+ componentStack: errorInfo.componentStack,
27523
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
27524
+ });
27525
+ this.setState((prev) => ({
27526
+ errorCount: prev.errorCount + 1,
27527
+ lastError: error,
27528
+ errorInfo
27529
+ }));
27530
+ try {
27531
+ if (typeof window !== "undefined" && window.mixpanel) {
27532
+ window.mixpanel.track("React Render Error", {
27533
+ error: error.message,
27534
+ component: errorInfo.componentStack?.split("\n")[1] || "unknown",
27535
+ errorCount: this.state.errorCount + 1
27536
+ });
27537
+ }
27538
+ } catch (analyticsError) {
27539
+ console.warn("[ErrorBoundary] Analytics tracking failed:", analyticsError);
27540
+ }
27541
+ }
27542
+ render() {
27543
+ if (!this.state.hasError) {
27544
+ return this.props.children;
27545
+ }
27546
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-screen w-screen items-center justify-center bg-slate-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center space-y-6 text-center", children: [
27547
+ /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "lg", message: "Loading Dashboard..." }),
27548
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500", children: "Taking longer than usual..." }),
27549
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
27550
+ "a",
27551
+ {
27552
+ href: "#",
27553
+ onClick: (e) => {
27554
+ e.preventDefault();
27555
+ this.handleClearAndReload();
27556
+ },
27557
+ className: "text-xs text-gray-400 hover:text-gray-600 underline transition-colors",
27558
+ children: "Reset and try again"
27559
+ }
27560
+ ) }),
27561
+ process.env.NODE_ENV === "development" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400 italic mt-4", children: "Check console for error details" })
27562
+ ] }) });
27563
+ }
27564
+ };
27370
27565
  var BackButton = ({
27371
27566
  onClick,
27372
27567
  text = "Back",
@@ -27731,70 +27926,6 @@ var NewClipsNotification = ({
27731
27926
  }
27732
27927
  );
27733
27928
  };
27734
- var getSupabaseClient2 = () => {
27735
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
27736
- const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
27737
- if (!url || !key) {
27738
- throw new Error("Supabase configuration missing");
27739
- }
27740
- return supabaseJs.createClient(url, key);
27741
- };
27742
- var getAuthToken3 = async () => {
27743
- try {
27744
- const supabase = getSupabaseClient2();
27745
- const { data: { session } } = await supabase.auth.getSession();
27746
- return session?.access_token || null;
27747
- } catch (error) {
27748
- console.error("[useWorkspaceCrop] Error getting auth token:", error);
27749
- return null;
27750
- }
27751
- };
27752
- function useWorkspaceCrop(workspaceId) {
27753
- const [crop, setCrop] = React23.useState(null);
27754
- const [isLoading, setIsLoading] = React23.useState(true);
27755
- const [error, setError] = React23.useState(null);
27756
- React23.useEffect(() => {
27757
- if (!workspaceId) {
27758
- setIsLoading(false);
27759
- return;
27760
- }
27761
- const fetchCrop = async () => {
27762
- setIsLoading(true);
27763
- setError(null);
27764
- try {
27765
- const token = await getAuthToken3();
27766
- if (!token) {
27767
- throw new Error("Authentication required");
27768
- }
27769
- const response = await fetch("/api/clips/supabase", {
27770
- method: "POST",
27771
- headers: {
27772
- "Content-Type": "application/json",
27773
- "Authorization": `Bearer ${token}`
27774
- },
27775
- body: JSON.stringify({
27776
- action: "crop",
27777
- workspaceId
27778
- })
27779
- });
27780
- if (!response.ok) {
27781
- throw new Error(`Failed to fetch crop: ${response.statusText}`);
27782
- }
27783
- const data = await response.json();
27784
- console.log(`[useWorkspaceCrop] Fetched crop for workspace ${workspaceId}:`, data.crop);
27785
- setCrop(data.crop);
27786
- } catch (err) {
27787
- console.error("[useWorkspaceCrop] Error fetching crop:", err);
27788
- setError(err instanceof Error ? err.message : "Failed to fetch crop configuration");
27789
- setCrop(null);
27790
- } finally {
27791
- setIsLoading(false);
27792
- }
27793
- };
27794
- fetchCrop();
27795
- }, [workspaceId]);
27796
- return { crop, isLoading, error };
27797
- }
27798
27929
  var parseCycleTime = (value) => {
27799
27930
  if (typeof value === "number" && Number.isFinite(value)) {
27800
27931
  return value;
@@ -29050,6 +29181,11 @@ var BottlenecksContent = ({
29050
29181
  return Number.isFinite(numericValue) ? numericValue : null;
29051
29182
  })();
29052
29183
  const videoRef = React23.useRef(null);
29184
+ const videoPlayerOptions = React23.useMemo(() => ({
29185
+ fluid: false,
29186
+ responsive: false,
29187
+ fill: false
29188
+ }), []);
29053
29189
  const [initialFilter, setInitialFilter] = React23.useState("");
29054
29190
  const currentIndexRef = React23.useRef(0);
29055
29191
  const activeFilterRef = React23.useRef(initialFilter);
@@ -29060,6 +29196,7 @@ var BottlenecksContent = ({
29060
29196
  const [duration, setDuration] = React23.useState(0);
29061
29197
  const [currentIndex, setCurrentIndex] = React23.useState(0);
29062
29198
  const [currentClipId, setCurrentClipId] = React23.useState(null);
29199
+ const [playbackSpeed, setPlaybackSpeed] = React23.useState(1);
29063
29200
  const [isTransitioning, setIsTransitioning] = React23.useState(false);
29064
29201
  const [pendingVideo, setPendingVideo] = React23.useState(null);
29065
29202
  const [isVideoBuffering, setIsVideoBuffering] = React23.useState(false);
@@ -29803,7 +29940,10 @@ var BottlenecksContent = ({
29803
29940
  if (error?.isRetrying) {
29804
29941
  setError(null);
29805
29942
  }
29806
- }, [error]);
29943
+ if (videoRef.current?.playbackRate && playbackSpeed !== 1) {
29944
+ videoRef.current.playbackRate(playbackSpeed);
29945
+ }
29946
+ }, [error, playbackSpeed]);
29807
29947
  const handleVideoPlay = React23.useCallback(async (player) => {
29808
29948
  setIsPlaying(true);
29809
29949
  setIsInitialLoading(false);
@@ -29973,7 +30113,13 @@ var BottlenecksContent = ({
29973
30113
  player.pause();
29974
30114
  }
29975
30115
  };
29976
- const toggleFullscreen = React23.useCallback((e) => {
30116
+ const handlePlaybackSpeedChange = React23.useCallback((speed) => {
30117
+ setPlaybackSpeed(speed);
30118
+ if (videoRef.current?.playbackRate) {
30119
+ videoRef.current.playbackRate(speed);
30120
+ }
30121
+ }, []);
30122
+ React23.useCallback((e) => {
29977
30123
  e.stopPropagation();
29978
30124
  setIsFullscreen((prev) => !prev);
29979
30125
  }, []);
@@ -30147,12 +30293,7 @@ var BottlenecksContent = ({
30147
30293
  onLoadedData: handleLoadedData,
30148
30294
  onPlaying: handleVideoPlaying,
30149
30295
  onLoadingChange: handleVideoLoadingChange,
30150
- options: {
30151
- // Ensure full height is always visible - no cropping
30152
- fluid: false,
30153
- responsive: false,
30154
- fill: false
30155
- }
30296
+ options: videoPlayerOptions
30156
30297
  }
30157
30298
  )
30158
30299
  }
@@ -30213,58 +30354,7 @@ var BottlenecksContent = ({
30213
30354
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium mr-2", children: getClipTypeLabel(currentVideo) }),
30214
30355
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-80 hidden sm:inline", children: currentVideo.description })
30215
30356
  ] }) })
30216
- ),
30217
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 p-3 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 z-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-white", children: [
30218
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
30219
- /* @__PURE__ */ jsxRuntime.jsx(
30220
- "button",
30221
- {
30222
- onClick: (e) => {
30223
- e.stopPropagation();
30224
- togglePlayback();
30225
- },
30226
- className: "p-1.5 hover:bg-white/20 rounded-full focus:outline-none focus:ring-2 focus:ring-white/50",
30227
- "aria-label": isPlaying ? "Pause" : "Play",
30228
- children: isPlaying ? /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 00-1 1v2a1 1 0 102 0V9a1 1 0 00-1-1zm5 0a1 1 0 00-1 1v2a1 1 0 102 0V9a1 1 0 00-1-1z", clipRule: "evenodd" }) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8.118l-.001 3.764a1 1 0 001.555.832l3.196-1.882a1 1 0 000-1.664l-3.196-1.882z", clipRule: "evenodd" }) })
30229
- }
30230
- ),
30231
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-mono px-2", children: [
30232
- formatTime2(currentTime),
30233
- " / ",
30234
- formatTime2(duration)
30235
- ] })
30236
- ] }),
30237
- /* @__PURE__ */ jsxRuntime.jsx(
30238
- "input",
30239
- {
30240
- type: "range",
30241
- min: "0",
30242
- max: duration || 0,
30243
- value: currentTime,
30244
- onChange: (e) => {
30245
- if (videoRef.current) {
30246
- videoRef.current.currentTime(Number(e.target.value));
30247
- }
30248
- },
30249
- className: "flex-grow mx-3 h-2.5 bg-white/30 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-white/50 touch-manipulation",
30250
- style: {
30251
- WebkitAppearance: "none",
30252
- appearance: "none"
30253
- },
30254
- "aria-label": "Seek slider"
30255
- }
30256
- ),
30257
- /* @__PURE__ */ jsxRuntime.jsx(
30258
- "button",
30259
- {
30260
- onClick: toggleFullscreen,
30261
- className: "p-1.5 hover:bg-white/20 rounded-full focus:outline-none focus:ring-2 focus:ring-white/50",
30262
- "aria-label": "Fullscreen",
30263
- title: "Expand to fullscreen",
30264
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-5 w-5" })
30265
- }
30266
- )
30267
- ] }) })
30357
+ )
30268
30358
  ] }) }) }) : (
30269
30359
  /* Priority 5: Show "no clips found" only if we have counts and there are truly no clips for workspace */
30270
30360
  hasInitialLoad && Object.keys(mergedCounts).length > 0 && Object.values(mergedCounts).every((count) => count === 0) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex items-center justify-center ${triageMode ? "h-full" : "h-[calc(100%-4rem)]"}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center p-8", children: [
@@ -30482,11 +30572,7 @@ var BottlenecksContent = ({
30482
30572
  onLoadedData: handleLoadedData,
30483
30573
  onPlaying: handleVideoPlaying,
30484
30574
  onLoadingChange: handleVideoLoadingChange,
30485
- options: {
30486
- fluid: false,
30487
- responsive: false,
30488
- fill: false
30489
- }
30575
+ options: videoPlayerOptions
30490
30576
  }
30491
30577
  )
30492
30578
  }
@@ -30494,45 +30580,32 @@ var BottlenecksContent = ({
30494
30580
  (isTransitioning || isVideoBuffering && isInitialLoading) && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
30495
30581
  !isTransitioning && isVideoBuffering && !isInitialLoading && !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-30 flex items-center justify-center bg-black/60", children: /* @__PURE__ */ jsxRuntime.jsx(OptifyeLogoLoader_default, { size: "md", message: "Loading video..." }) }),
30496
30582
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 z-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-white", children: [
30497
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
30498
- /* @__PURE__ */ jsxRuntime.jsx(
30499
- "button",
30583
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
30584
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Speed:" }),
30585
+ /* @__PURE__ */ jsxRuntime.jsxs(
30586
+ "select",
30500
30587
  {
30501
- onClick: (e) => {
30502
- e.stopPropagation();
30503
- togglePlayback();
30504
- },
30505
- className: "p-2 hover:bg-white/20 rounded-full focus:outline-none focus:ring-2 focus:ring-white/50",
30506
- "aria-label": isPlaying ? "Pause" : "Play",
30507
- children: isPlaying ? /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-6 w-6", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 00-1 1v2a1 1 0 102 0V9a1 1 0 00-1-1zm5 0a1 1 0 00-1 1v2a1 1 0 102 0V9a1 1 0 00-1-1z", clipRule: "evenodd" }) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-6 w-6", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8.118l-.001 3.764a1 1 0 001.555.832l3.196-1.882a1 1 0 000-1.664l-3.196-1.882z", clipRule: "evenodd" }) })
30588
+ value: playbackSpeed,
30589
+ onChange: (e) => handlePlaybackSpeedChange(Number(e.target.value)),
30590
+ className: "px-2 py-1 bg-white/20 hover:bg-white/30 rounded text-sm font-medium cursor-pointer focus:outline-none focus:ring-2 focus:ring-white/50 transition-colors",
30591
+ "aria-label": "Playback speed",
30592
+ children: [
30593
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "0.25", children: "0.25x" }),
30594
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "0.5", children: "0.5x" }),
30595
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "0.75", children: "0.75x" }),
30596
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "1", children: "1x" }),
30597
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "1.25", children: "1.25x" }),
30598
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "1.5", children: "1.5x" }),
30599
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "1.75", children: "1.75x" }),
30600
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "2", children: "2x" }),
30601
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "3", children: "3x" }),
30602
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "4", children: "4x" }),
30603
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "5", children: "5x" }),
30604
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "10", children: "10x" })
30605
+ ]
30508
30606
  }
30509
- ),
30510
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-mono px-2", children: [
30511
- formatTime2(currentTime),
30512
- " / ",
30513
- formatTime2(duration)
30514
- ] })
30607
+ )
30515
30608
  ] }),
30516
- /* @__PURE__ */ jsxRuntime.jsx(
30517
- "input",
30518
- {
30519
- type: "range",
30520
- min: "0",
30521
- max: duration || 0,
30522
- value: currentTime,
30523
- onChange: (e) => {
30524
- if (videoRef.current) {
30525
- videoRef.current.currentTime(Number(e.target.value));
30526
- }
30527
- },
30528
- className: "flex-grow mx-4 h-2.5 bg-white/30 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-white/50 touch-manipulation",
30529
- style: {
30530
- WebkitAppearance: "none",
30531
- appearance: "none"
30532
- },
30533
- "aria-label": "Seek slider"
30534
- }
30535
- ),
30536
30609
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
30537
30610
  /* @__PURE__ */ jsxRuntime.jsx(
30538
30611
  "button",
@@ -37633,26 +37706,26 @@ var SingleVideoStream = ({
37633
37706
  const hlsStreamUrl = streamUrl || getCameraStreamUrl(workspaceName, baseUrl);
37634
37707
  console.log(`Using camera URL for ${workspaceName}: ${hlsStreamUrl}`);
37635
37708
  const mergedHlsConfig = { ...DEFAULT_HLS_CONFIG, ...hlsConfig };
37636
- if (Hls2__default.default.isSupported()) {
37637
- const hls = new Hls2__default.default(mergedHlsConfig);
37709
+ if (Hls3__default.default.isSupported()) {
37710
+ const hls = new Hls3__default.default(mergedHlsConfig);
37638
37711
  hlsRef.current = hls;
37639
- hls.on(Hls2__default.default.Events.MEDIA_ATTACHED, () => {
37712
+ hls.on(Hls3__default.default.Events.MEDIA_ATTACHED, () => {
37640
37713
  console.log("HLS media attached");
37641
37714
  hls.loadSource(hlsStreamUrl);
37642
37715
  });
37643
- hls.on(Hls2__default.default.Events.MANIFEST_PARSED, () => {
37716
+ hls.on(Hls3__default.default.Events.MANIFEST_PARSED, () => {
37644
37717
  console.log("HLS manifest parsed");
37645
37718
  attemptPlay(video);
37646
37719
  });
37647
- hls.on(Hls2__default.default.Events.ERROR, (_, data) => {
37720
+ hls.on(Hls3__default.default.Events.ERROR, (_, data) => {
37648
37721
  if (data.fatal) {
37649
37722
  console.error("Fatal HLS error:", data.type, data.details);
37650
37723
  switch (data.type) {
37651
- case Hls2__default.default.ErrorTypes.NETWORK_ERROR:
37724
+ case Hls3__default.default.ErrorTypes.NETWORK_ERROR:
37652
37725
  console.error("Fatal network error encountered");
37653
37726
  setError("Network error: Please check your connection");
37654
37727
  break;
37655
- case Hls2__default.default.ErrorTypes.MEDIA_ERROR:
37728
+ case Hls3__default.default.ErrorTypes.MEDIA_ERROR:
37656
37729
  console.error("Fatal media error encountered, trying to recover");
37657
37730
  hls.recoverMediaError();
37658
37731
  break;
@@ -42413,9 +42486,9 @@ var MetricCards = React23.memo(({ lineInfo }) => {
42413
42486
  animate: "animate",
42414
42487
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 mb-2 md:h-[35vh] h-auto",
42415
42488
  children: [
42416
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[200px] sm:h-[280px] md:h-auto lg:col-span-1 sm:col-span-2 lg:col-span-1", children: [
42417
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-semibold text-gray-700 mb-1 sm:mb-2 text-center", children: "Line Output" }),
42418
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100%-1.75rem)] sm:h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
42489
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
42490
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-semibold text-gray-700 mb-2 text-center", children: "Line Output" }),
42491
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
42419
42492
  OutputProgressChart,
42420
42493
  {
42421
42494
  currentOutput: lineInfo?.metrics.current_output || 0,
@@ -42423,9 +42496,9 @@ var MetricCards = React23.memo(({ lineInfo }) => {
42423
42496
  }
42424
42497
  ) }) })
42425
42498
  ] }),
42426
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[140px] sm:h-[160px] md:h-auto", children: [
42499
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
42427
42500
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-semibold text-gray-700 text-center mb-2", children: "Underperforming Workspaces" }),
42428
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center h-[calc(100%-2rem)] sm:h-[calc(100%-2.5rem)]", children: [
42501
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center h-[calc(100%-2.5rem)]", children: [
42429
42502
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold text-red-600", children: lineInfo?.metrics.underperforming_workspaces }),
42430
42503
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xl sm:text-2xl md:text-2xl lg:text-3xl text-gray-500 ml-1 sm:ml-2", children: [
42431
42504
  "/ ",
@@ -42433,9 +42506,9 @@ var MetricCards = React23.memo(({ lineInfo }) => {
42433
42506
  ] })
42434
42507
  ] })
42435
42508
  ] }),
42436
- /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[140px] sm:h-[160px] md:h-auto", children: [
42509
+ /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { variants: itemVariants, className: "bg-white rounded-xl shadow-sm p-3 sm:p-4 overflow-hidden h-[240px] sm:h-[260px] md:h-auto", children: [
42437
42510
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm sm:text-base font-semibold text-gray-700 text-center mb-2", children: "Average Efficiency" }),
42438
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-2rem)] sm:h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold ${(lineInfo?.metrics.avg_efficiency || 0) >= 90 ? "text-green-600" : (lineInfo?.metrics.avg_efficiency || 0) >= 70 ? "text-yellow-600" : "text-red-600"}`, children: [
42511
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-[calc(100%-2.5rem)]", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold ${(lineInfo?.metrics.avg_efficiency || 0) >= 90 ? "text-green-600" : (lineInfo?.metrics.avg_efficiency || 0) >= 70 ? "text-yellow-600" : "text-red-600"}`, children: [
42439
42512
  lineInfo?.metrics.avg_efficiency.toFixed(1),
42440
42513
  "%"
42441
42514
  ] }) })
@@ -51992,6 +52065,7 @@ exports.CardTitle = CardTitle2;
51992
52065
  exports.ClipFilterProvider = ClipFilterProvider;
51993
52066
  exports.CompactWorkspaceHealthCard = CompactWorkspaceHealthCard;
51994
52067
  exports.CongratulationsOverlay = CongratulationsOverlay;
52068
+ exports.CroppedHlsVideoPlayer = CroppedHlsVideoPlayer;
51995
52069
  exports.CroppedVideoPlayer = CroppedVideoPlayer;
51996
52070
  exports.CycleTimeChart = CycleTimeChart;
51997
52071
  exports.CycleTimeOverTimeChart = CycleTimeOverTimeChart;
@@ -52032,6 +52106,7 @@ exports.Header = Header;
52032
52106
  exports.HealthStatusGrid = HealthStatusGrid;
52033
52107
  exports.HealthStatusIndicator = HealthStatusIndicator;
52034
52108
  exports.HelpView = HelpView_default;
52109
+ exports.HlsVideoPlayer = HlsVideoPlayer;
52035
52110
  exports.HomeView = HomeView_default;
52036
52111
  exports.HourlyOutputChart = HourlyOutputChart2;
52037
52112
  exports.ISTTimer = ISTTimer_default;