@optifye/dashboard-core 6.10.21 → 6.10.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -7,7 +7,7 @@ import { startOfMonth, endOfMonth, eachDayOfInterval, getDay, isSameDay, isWithi
7
7
  import mixpanel from 'mixpanel-browser';
8
8
  import { EventEmitter } from 'events';
9
9
  import { createClient, REALTIME_SUBSCRIBE_STATES } from '@supabase/supabase-js';
10
- import Hls3, { Events, ErrorTypes } from 'hls.js';
10
+ import Hls, { Events, ErrorTypes } from 'hls.js';
11
11
  import useSWR from 'swr';
12
12
  import { noop, warning, invariant, progress, secondsToMilliseconds, millisecondsToSeconds, memo as memo$1 } from 'motion-utils';
13
13
  import { getValueTransition, hover, press, isPrimaryPointer, GroupPlaybackControls, setDragLock, supportsLinearEasing, attachTimeline, isGenerator, calcGeneratorDuration, isWaapiSupportedEasing, mapEasingToNativeEasing, maxGeneratorDuration, generateLinearEasing, isBezierDefinition } from 'motion-dom';
@@ -429,6 +429,9 @@ var _getSupabaseInstance = () => {
429
429
  }
430
430
  return supabaseInstance;
431
431
  };
432
+ var _getSupabaseInstanceOptional = () => {
433
+ return supabaseInstance;
434
+ };
432
435
 
433
436
  // src/lib/services/actionService.ts
434
437
  var actionService = {
@@ -2060,6 +2063,11 @@ var workspaceService = {
2060
2063
  _workspacesInFlight: /* @__PURE__ */ new Map(),
2061
2064
  _workspacesCacheExpiryMs: 10 * 60 * 1e3,
2062
2065
  // 10 minutes cache
2066
+ // Cache for workspace video streams (R2 mapping + crops)
2067
+ _workspaceVideoStreamsCache: /* @__PURE__ */ new Map(),
2068
+ _workspaceVideoStreamsInFlight: /* @__PURE__ */ new Map(),
2069
+ _workspaceVideoStreamsCacheExpiryMs: 5 * 60 * 1e3,
2070
+ // 5 minutes cache
2063
2071
  async getWorkspaces(lineId, options) {
2064
2072
  const enabledOnly = options?.enabledOnly ?? false;
2065
2073
  const force = options?.force ?? false;
@@ -2106,6 +2114,61 @@ var workspaceService = {
2106
2114
  async getEnabledWorkspaces(lineId, options) {
2107
2115
  return this.getWorkspaces(lineId, { enabledOnly: true, force: options?.force });
2108
2116
  },
2117
+ async getWorkspaceVideoStreams(params) {
2118
+ const workspaceIds = (params.workspaceIds || []).filter(Boolean);
2119
+ const lineIds = (params.lineIds || []).filter(Boolean);
2120
+ const force = params.force ?? false;
2121
+ if (!workspaceIds.length && !lineIds.length) {
2122
+ return {};
2123
+ }
2124
+ const workspaceKey = workspaceIds.slice().sort().join(",");
2125
+ const lineKey = lineIds.slice().sort().join(",");
2126
+ const cacheKey = workspaceKey ? `workspaces:${workspaceKey}` : `lines:${lineKey}`;
2127
+ const now2 = Date.now();
2128
+ const cached = this._workspaceVideoStreamsCache.get(cacheKey);
2129
+ if (!force && cached && now2 - cached.timestamp < this._workspaceVideoStreamsCacheExpiryMs) {
2130
+ return cached.streams;
2131
+ }
2132
+ const inFlight = this._workspaceVideoStreamsInFlight.get(cacheKey);
2133
+ if (!force && inFlight) {
2134
+ return inFlight;
2135
+ }
2136
+ const fetchPromise = (async () => {
2137
+ try {
2138
+ const token = await getAuthToken2();
2139
+ const apiUrl = getBackendUrl2();
2140
+ const response = await fetch(`${apiUrl}/api/workspaces/video-streams`, {
2141
+ method: "POST",
2142
+ headers: {
2143
+ "Authorization": `Bearer ${token}`,
2144
+ "Content-Type": "application/json"
2145
+ },
2146
+ body: JSON.stringify({
2147
+ workspace_ids: workspaceIds.length ? workspaceIds : void 0,
2148
+ line_ids: lineIds.length ? lineIds : void 0
2149
+ })
2150
+ });
2151
+ if (!response.ok) {
2152
+ const errorText = await response.text();
2153
+ throw new Error(`Backend API error (${response.status}): ${errorText}`);
2154
+ }
2155
+ const data = await response.json();
2156
+ const streams = data.streams || {};
2157
+ this._workspaceVideoStreamsCache.set(cacheKey, {
2158
+ streams,
2159
+ timestamp: Date.now()
2160
+ });
2161
+ return streams;
2162
+ } catch (error) {
2163
+ console.error("Error fetching workspace video streams:", error);
2164
+ throw error;
2165
+ } finally {
2166
+ this._workspaceVideoStreamsInFlight.delete(cacheKey);
2167
+ }
2168
+ })();
2169
+ this._workspaceVideoStreamsInFlight.set(cacheKey, fetchPromise);
2170
+ return fetchPromise;
2171
+ },
2109
2172
  /**
2110
2173
  * Fetches workspace display names from the database
2111
2174
  * Returns a map of workspace_id -> display_name
@@ -4915,13 +4978,98 @@ function isValidShiftId(shiftId) {
4915
4978
  const id3 = typeof shiftId === "string" ? parseInt(shiftId, 10) : shiftId;
4916
4979
  return Number.isFinite(id3) && id3 >= 0;
4917
4980
  }
4981
+ var createSupabaseClient = (url, key) => createClient(url, key, {
4982
+ auth: {
4983
+ autoRefreshToken: true,
4984
+ persistSession: true,
4985
+ detectSessionInUrl: true,
4986
+ flowType: "pkce",
4987
+ // Enable debug logging in development
4988
+ debug: process.env.NODE_ENV === "development"
4989
+ },
4990
+ db: {
4991
+ schema: "public"
4992
+ // Default schema, we'll use .schema('ai') in queries
4993
+ },
4994
+ global: {
4995
+ headers: {
4996
+ "x-application-name": "optifye-dashboard"
4997
+ },
4998
+ // Add global fetch timeout (5 minutes)
4999
+ fetch: async (url2, options = {}) => {
5000
+ const controller = new AbortController();
5001
+ const timeoutId = setTimeout(() => controller.abort(), 3e5);
5002
+ try {
5003
+ const response = await fetch(url2, {
5004
+ ...options,
5005
+ signal: controller.signal
5006
+ });
5007
+ clearTimeout(timeoutId);
5008
+ return response;
5009
+ } catch (error) {
5010
+ clearTimeout(timeoutId);
5011
+ throw error;
5012
+ }
5013
+ }
5014
+ }
5015
+ });
5016
+ var getAnonClient = () => {
5017
+ const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
5018
+ const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
5019
+ if (!url || !key) {
5020
+ console.warn("[dashboard-core] Missing supabase env variables");
5021
+ }
5022
+ return createClient(url, key, {
5023
+ auth: {
5024
+ autoRefreshToken: true,
5025
+ persistSession: true,
5026
+ detectSessionInUrl: true,
5027
+ flowType: "pkce",
5028
+ debug: process.env.NODE_ENV === "development"
5029
+ },
5030
+ global: {
5031
+ headers: {
5032
+ "x-application-name": "optifye-dashboard"
5033
+ },
5034
+ // Add global fetch timeout (5 minutes)
5035
+ fetch: async (url2, options = {}) => {
5036
+ const controller = new AbortController();
5037
+ const timeoutId = setTimeout(() => controller.abort(), 3e5);
5038
+ try {
5039
+ const response = await fetch(url2, {
5040
+ ...options,
5041
+ signal: controller.signal
5042
+ });
5043
+ clearTimeout(timeoutId);
5044
+ return response;
5045
+ } catch (error) {
5046
+ clearTimeout(timeoutId);
5047
+ throw error;
5048
+ }
5049
+ }
5050
+ }
5051
+ });
5052
+ };
5053
+
5054
+ // src/lib/api/s3-clips-supabase.ts
5055
+ var cachedClient = null;
5056
+ var cachedConfig = null;
4918
5057
  var getSupabaseClient = () => {
5058
+ const existing = _getSupabaseInstanceOptional();
5059
+ if (existing) {
5060
+ return existing;
5061
+ }
4919
5062
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
4920
5063
  const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
4921
5064
  if (!url || !key) {
4922
5065
  throw new Error("Supabase configuration missing");
4923
5066
  }
4924
- return createClient(url, key);
5067
+ if (!cachedClient || cachedConfig?.url !== url || cachedConfig?.key !== key) {
5068
+ cachedClient = createSupabaseClient(url, key);
5069
+ cachedConfig = { url, key };
5070
+ _setSupabaseInstance(cachedClient);
5071
+ }
5072
+ return cachedClient;
4925
5073
  };
4926
5074
  var getAuthToken3 = async () => {
4927
5075
  try {
@@ -5983,9 +6131,13 @@ var createSupervisorService = (supabase) => {
5983
6131
  var simulateApiDelay = (ms = 1e3) => {
5984
6132
  return new Promise((resolve) => setTimeout(resolve, ms));
5985
6133
  };
6134
+
6135
+ // src/lib/services/timezone.ts
5986
6136
  var TimezoneService = class _TimezoneService {
5987
6137
  constructor() {
5988
6138
  this.timezoneCache = /* @__PURE__ */ new Map();
6139
+ this.fallbackClient = null;
6140
+ this.fallbackConfig = null;
5989
6141
  this.supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
5990
6142
  this.supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
5991
6143
  }
@@ -5995,6 +6147,21 @@ var TimezoneService = class _TimezoneService {
5995
6147
  }
5996
6148
  return _TimezoneService.instance;
5997
6149
  }
6150
+ getClient() {
6151
+ const existing = _getSupabaseInstanceOptional();
6152
+ if (existing) {
6153
+ return existing;
6154
+ }
6155
+ if (!this.supabaseUrl || !this.supabaseAnonKey) {
6156
+ throw new Error("Supabase configuration missing");
6157
+ }
6158
+ if (!this.fallbackClient || this.fallbackConfig?.url !== this.supabaseUrl || this.fallbackConfig?.key !== this.supabaseAnonKey) {
6159
+ this.fallbackClient = createSupabaseClient(this.supabaseUrl, this.supabaseAnonKey);
6160
+ this.fallbackConfig = { url: this.supabaseUrl, key: this.supabaseAnonKey };
6161
+ _setSupabaseInstance(this.fallbackClient);
6162
+ }
6163
+ return this.fallbackClient;
6164
+ }
5998
6165
  /**
5999
6166
  * Fetch timezone for a specific line from Supabase
6000
6167
  */
@@ -6003,7 +6170,7 @@ var TimezoneService = class _TimezoneService {
6003
6170
  return this.timezoneCache.get(lineId);
6004
6171
  }
6005
6172
  try {
6006
- const supabase = createClient(this.supabaseUrl, this.supabaseAnonKey);
6173
+ const supabase = this.getClient();
6007
6174
  const { data, error } = await supabase.from("line_operating_hours").select("timezone").eq("line_id", lineId).order("created_at", { ascending: false }).limit(1).single();
6008
6175
  if (error) {
6009
6176
  console.warn(`Failed to fetch timezone for line ${lineId}:`, error);
@@ -6028,7 +6195,7 @@ var TimezoneService = class _TimezoneService {
6028
6195
  return this.timezoneCache.get(cacheKey);
6029
6196
  }
6030
6197
  try {
6031
- const supabase = createClient(this.supabaseUrl, this.supabaseAnonKey);
6198
+ const supabase = this.getClient();
6032
6199
  const { data, error } = await supabase.from("line_operating_hours").select(`
6033
6200
  timezone,
6034
6201
  line:lines!inner(
@@ -6058,7 +6225,7 @@ var TimezoneService = class _TimezoneService {
6058
6225
  return this.timezoneCache.get(cacheKey);
6059
6226
  }
6060
6227
  try {
6061
- const supabase = createClient(this.supabaseUrl, this.supabaseAnonKey);
6228
+ const supabase = this.getClient();
6062
6229
  const { data: workspaceData, error: workspaceError } = await supabase.from("workspaces").select("line_id").eq("id", workspaceId).single();
6063
6230
  if (workspaceError) {
6064
6231
  console.warn(`Failed to fetch workspace ${workspaceId}:`, workspaceError);
@@ -6090,7 +6257,7 @@ var TimezoneService = class _TimezoneService {
6090
6257
  return result;
6091
6258
  }
6092
6259
  try {
6093
- const supabase = createClient(this.supabaseUrl, this.supabaseAnonKey);
6260
+ const supabase = this.getClient();
6094
6261
  const { data, error } = await supabase.from("line_operating_hours").select("line_id, timezone").in("line_id", uncachedLineIds).order("created_at", { ascending: false });
6095
6262
  if (error) {
6096
6263
  console.warn("Failed to fetch timezones for lines:", error);
@@ -7108,12 +7275,16 @@ var SupabaseProvider = ({ client, children }) => {
7108
7275
  const contextValue = useMemo(() => ({ supabase: client }), [client]);
7109
7276
  return /* @__PURE__ */ jsx(SupabaseContext.Provider, { value: contextValue, children });
7110
7277
  };
7111
- var useSupabase = () => {
7278
+ var useOptionalSupabase = () => {
7112
7279
  const context = useContext(SupabaseContext);
7113
- if (context === void 0) {
7280
+ return context?.supabase ?? null;
7281
+ };
7282
+ var useSupabase = () => {
7283
+ const client = useOptionalSupabase();
7284
+ if (!client) {
7114
7285
  throw new Error("useSupabase must be used within a SupabaseProvider.");
7115
7286
  }
7116
- return context.supabase;
7287
+ return client;
7117
7288
  };
7118
7289
 
7119
7290
  // src/lib/hooks/useSessionKeepAlive.ts
@@ -10870,16 +11041,63 @@ var useWorkspaceOperators = (workspaceId, options) => {
10870
11041
  refetch: fetchData
10871
11042
  };
10872
11043
  };
11044
+
11045
+ // src/lib/services/hlsAuthService.ts
11046
+ async function getAuthTokenForHls(supabase) {
11047
+ try {
11048
+ const { data: { session } } = await supabase.auth.getSession();
11049
+ if (!session?.access_token) {
11050
+ console.warn("[HLS Auth] No active session, R2 streaming may fail");
11051
+ return null;
11052
+ }
11053
+ console.log("[HLS Auth] Retrieved token for HLS.js requests");
11054
+ return session.access_token;
11055
+ } catch (error) {
11056
+ console.error("[HLS Auth] Error getting auth token:", error);
11057
+ return null;
11058
+ }
11059
+ }
11060
+
11061
+ // src/lib/utils/r2Detection.ts
11062
+ function isR2WorkerUrl(url, r2WorkerDomain) {
11063
+ if (!url || !r2WorkerDomain) return false;
11064
+ try {
11065
+ const workerDomain = new URL(r2WorkerDomain).hostname;
11066
+ return url.includes(workerDomain);
11067
+ } catch {
11068
+ return url.includes(r2WorkerDomain.replace(/https?:\/\//, ""));
11069
+ }
11070
+ }
11071
+
11072
+ // src/lib/utils/browser.ts
11073
+ function isSafari() {
11074
+ if (typeof window === "undefined") return false;
11075
+ const ua = window.navigator.userAgent;
11076
+ const isSafariUA = /^((?!chrome|android).)*safari/i.test(ua);
11077
+ const isIOS = /iPad|iPhone|iPod/.test(ua) && !window.MSStream;
11078
+ return isSafariUA || isIOS;
11079
+ }
11080
+ function getBrowserName() {
11081
+ if (typeof window === "undefined") return "server";
11082
+ const ua = window.navigator.userAgent;
11083
+ if (/chrome/i.test(ua) && !/edge/i.test(ua)) return "chrome";
11084
+ if (/safari/i.test(ua) && !/chrome/i.test(ua)) return "safari";
11085
+ if (/firefox/i.test(ua)) return "firefox";
11086
+ if (/edge/i.test(ua)) return "edge";
11087
+ return "unknown";
11088
+ }
11089
+
11090
+ // src/lib/hooks/useHlsStream.ts
10873
11091
  var HLS_CONFIG = {
10874
- // Buffer configuration for instant playback
10875
- maxBufferLength: 30,
10876
- // Increased for smoother playback
11092
+ // Buffer configuration for 60s segments
11093
+ maxBufferLength: 180,
11094
+ // Allow ~3 segments buffered for stability
10877
11095
  maxMaxBufferLength: 600,
10878
11096
  // 10 minutes max buffer
10879
11097
  maxBufferSize: 120 * 1e3 * 1e3,
10880
11098
  // 120MB buffer size
10881
- maxBufferHole: 0.1,
10882
- // Minimal tolerance for buffer holes
11099
+ maxBufferHole: 0.5,
11100
+ // Tolerate minor timestamp gaps
10883
11101
  // Low latency optimizations
10884
11102
  lowLatencyMode: false,
10885
11103
  // We prioritize stability over latency
@@ -10890,7 +11108,7 @@ var HLS_CONFIG = {
10890
11108
  // Auto-select quality
10891
11109
  autoStartLoad: true,
10892
11110
  startPosition: -1,
10893
- // Start from beginning
11111
+ // Start at live edge when available
10894
11112
  // Network optimization
10895
11113
  manifestLoadingMaxRetry: 6,
10896
11114
  levelLoadingMaxRetry: 6,
@@ -10899,9 +11117,10 @@ var HLS_CONFIG = {
10899
11117
  // Faster retries
10900
11118
  levelLoadingRetryDelay: 250,
10901
11119
  fragLoadingRetryDelay: 250,
10902
- manifestLoadingTimeOut: 2e4,
10903
- levelLoadingTimeOut: 2e4,
10904
- fragLoadingTimeOut: 2e4,
11120
+ manifestLoadingTimeOut: 9e4,
11121
+ levelLoadingTimeOut: 9e4,
11122
+ fragLoadingTimeOut: 9e4,
11123
+ maxConcurrentLoading: 2,
10905
11124
  // ABR (Adaptive Bitrate) settings
10906
11125
  abrEwmaFastLive: 3,
10907
11126
  abrEwmaSlowLive: 9,
@@ -10913,7 +11132,7 @@ var HLS_CONFIG = {
10913
11132
  abrBandWidthUpFactor: 0.7,
10914
11133
  abrMaxWithRealBitrate: true,
10915
11134
  // Fragment loading optimization
10916
- maxFragLookUpTolerance: 0.25,
11135
+ maxFragLookUpTolerance: 1,
10917
11136
  // Enable all optimizations
10918
11137
  enableCEA708Captions: false,
10919
11138
  // Disable if not needed
@@ -10923,12 +11142,21 @@ var HLS_CONFIG = {
10923
11142
  // Disable if not needed
10924
11143
  // Progressive loading
10925
11144
  progressive: true,
10926
- liveSyncDurationCount: 2
10927
- // Follow live edge aggressively
11145
+ liveSyncDurationCount: 2,
11146
+ // Stay ~2 segments behind live for a full segment buffer
11147
+ liveMaxLatencyDurationCount: 3,
11148
+ // Cap drift to ~3 segments
11149
+ maxLiveSyncPlaybackRate: 1
11150
+ // Do not chase live edge; prioritize continuity
10928
11151
  };
10929
11152
  var failedUrls = /* @__PURE__ */ new Map();
10930
11153
  var MAX_FAILURES_PER_URL = 3;
10931
11154
  var FAILURE_EXPIRY_MS = 5 * 60 * 1e3;
11155
+ var LIVE_RELOAD_MIN_INTERVAL_MS = 15 * 1e3;
11156
+ var DEFAULT_LIVE_OFFSET_SECONDS = 120;
11157
+ var DEFAULT_MAX_MANIFEST_AGE_MS = 10 * 60 * 1e3;
11158
+ var STALE_MANIFEST_POLL_INITIAL_DELAY_MS = 15 * 1e3;
11159
+ var STALE_MANIFEST_POLL_MAX_DELAY_MS = 60 * 1e3;
10932
11160
  function resetFailedUrl(url) {
10933
11161
  if (failedUrls.has(url)) {
10934
11162
  console.log(`[HLS] Manually resetting failed URL: ${url}`);
@@ -10939,7 +11167,11 @@ function isUrlPermanentlyFailed(url) {
10939
11167
  const failure = failedUrls.get(url);
10940
11168
  return failure?.permanentlyFailed || false;
10941
11169
  }
10942
- function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
11170
+ function useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig }) {
11171
+ const latestSrcRef = useRef(src);
11172
+ latestSrcRef.current = src;
11173
+ const shouldPlayRef = useRef(shouldPlay);
11174
+ shouldPlayRef.current = shouldPlay;
10943
11175
  const urlFailure = failedUrls.get(src);
10944
11176
  const isPermanentlyFailed = urlFailure?.permanentlyFailed || false;
10945
11177
  const cleanupFailedUrls = () => {
@@ -10962,7 +11194,194 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
10962
11194
  const lastTimeUpdateRef = useRef(0);
10963
11195
  const softRestartCountRef = useRef(0);
10964
11196
  const isNativeHlsRef = useRef(false);
11197
+ const playbackRateIntervalRef = useRef(null);
10965
11198
  const waitingTimerRef = useRef(null);
11199
+ const playRetryTimerRef = useRef(null);
11200
+ const playRetryCountRef = useRef(0);
11201
+ const manifestWatchdogRef = useRef(null);
11202
+ const lastHiddenAtRef = useRef(null);
11203
+ const manifestRetryTimerRef = useRef(null);
11204
+ const manifestRetryDelayRef = useRef(5e3);
11205
+ const lastLiveReloadRef = useRef(0);
11206
+ const isR2StreamRef = useRef(false);
11207
+ const nativeStreamUrlRef = useRef(null);
11208
+ const forcedLiveStartRef = useRef(false);
11209
+ const activeStreamUrlRef = useRef(null);
11210
+ const targetDurationRef = useRef(null);
11211
+ const lastManifestLoadRef = useRef(0);
11212
+ const lastFragLoadRef = useRef(0);
11213
+ const lastFragSnRef = useRef(null);
11214
+ const lastWindowStartSnRef = useRef(null);
11215
+ const lastWindowEndSnRef = useRef(null);
11216
+ const lastFragTimeoutSnRef = useRef(null);
11217
+ const lastFragTimeoutAtRef = useRef(null);
11218
+ const fragTimeoutCountRef = useRef(0);
11219
+ const lastManifestEndSnRef = useRef(null);
11220
+ const lastManifestEndSnUpdatedAtRef = useRef(null);
11221
+ const staleManifestTriggeredRef = useRef(false);
11222
+ const staleManifestUrlRef = useRef(null);
11223
+ const staleManifestEndSnRef = useRef(null);
11224
+ const staleManifestPollTimerRef = useRef(null);
11225
+ const staleManifestPollDelayRef = useRef(STALE_MANIFEST_POLL_INITIAL_DELAY_MS);
11226
+ const authTokenRef = useRef(null);
11227
+ const proxyEnabled = process.env.NEXT_PUBLIC_HLS_PROXY_ENABLED === "true";
11228
+ const proxyBaseUrl = (process.env.NEXT_PUBLIC_HLS_PROXY_URL || "/api/stream").replace(/\/$/, "");
11229
+ const debugEnabled = process.env.NEXT_PUBLIC_HLS_DEBUG === "true";
11230
+ const maxManifestAgeMs = (() => {
11231
+ const raw = process.env.NEXT_PUBLIC_HLS_MAX_MANIFEST_AGE_MS;
11232
+ const parsed = raw ? Number(raw) : NaN;
11233
+ return Number.isFinite(parsed) ? parsed : DEFAULT_MAX_MANIFEST_AGE_MS;
11234
+ })();
11235
+ const debugLog = (...args) => {
11236
+ if (debugEnabled) {
11237
+ console.log(...args);
11238
+ }
11239
+ };
11240
+ const getProgramDateTimeMs = (value) => {
11241
+ if (value instanceof Date) return value.getTime();
11242
+ if (typeof value === "number") return value;
11243
+ if (typeof value === "string") {
11244
+ const parsed = Date.parse(value);
11245
+ return Number.isNaN(parsed) ? null : parsed;
11246
+ }
11247
+ return null;
11248
+ };
11249
+ const parseManifestStatus = (manifestText) => {
11250
+ let mediaSequence = null;
11251
+ let segmentCount = 0;
11252
+ let lastProgramDateTimeMs = null;
11253
+ const lines = manifestText.split(/\r?\n/);
11254
+ for (const rawLine of lines) {
11255
+ const line = rawLine.trim();
11256
+ if (!line) continue;
11257
+ if (line.startsWith("#EXT-X-MEDIA-SEQUENCE:")) {
11258
+ const value = Number.parseInt(line.replace("#EXT-X-MEDIA-SEQUENCE:", ""), 10);
11259
+ if (Number.isFinite(value)) {
11260
+ mediaSequence = value;
11261
+ }
11262
+ } else if (line.startsWith("#EXT-X-PROGRAM-DATE-TIME:")) {
11263
+ const timestamp = line.replace("#EXT-X-PROGRAM-DATE-TIME:", "").trim();
11264
+ const parsed = Date.parse(timestamp);
11265
+ if (!Number.isNaN(parsed)) {
11266
+ lastProgramDateTimeMs = parsed;
11267
+ }
11268
+ } else if (line.startsWith("#EXTINF:")) {
11269
+ segmentCount += 1;
11270
+ }
11271
+ }
11272
+ let lastSequenceNumber = null;
11273
+ if (mediaSequence !== null && segmentCount > 0) {
11274
+ lastSequenceNumber = mediaSequence + segmentCount - 1;
11275
+ }
11276
+ return { lastProgramDateTimeMs, lastSequenceNumber };
11277
+ };
11278
+ const stopStaleManifestPolling = () => {
11279
+ if (staleManifestPollTimerRef.current) {
11280
+ clearTimeout(staleManifestPollTimerRef.current);
11281
+ staleManifestPollTimerRef.current = null;
11282
+ }
11283
+ staleManifestTriggeredRef.current = false;
11284
+ staleManifestUrlRef.current = null;
11285
+ staleManifestEndSnRef.current = null;
11286
+ staleManifestPollDelayRef.current = STALE_MANIFEST_POLL_INITIAL_DELAY_MS;
11287
+ };
11288
+ const pollStaleManifestOnce = async () => {
11289
+ if (!staleManifestTriggeredRef.current) return;
11290
+ if (!shouldPlayRef.current) {
11291
+ stopStaleManifestPolling();
11292
+ return;
11293
+ }
11294
+ const manifestUrl = staleManifestUrlRef.current;
11295
+ if (!manifestUrl) {
11296
+ stopStaleManifestPolling();
11297
+ return;
11298
+ }
11299
+ try {
11300
+ const headers = {};
11301
+ if (authTokenRef.current) {
11302
+ headers.Authorization = `Bearer ${authTokenRef.current}`;
11303
+ }
11304
+ const response = await fetch(buildCacheBustedUrl(manifestUrl), {
11305
+ method: "GET",
11306
+ cache: "no-store",
11307
+ headers
11308
+ });
11309
+ if (!response.ok) {
11310
+ throw new Error(`Manifest poll failed: ${response.status}`);
11311
+ }
11312
+ const manifestText = await response.text();
11313
+ const { lastProgramDateTimeMs, lastSequenceNumber } = parseManifestStatus(manifestText);
11314
+ const now2 = Date.now();
11315
+ const isFreshByProgramDateTime = lastProgramDateTimeMs !== null && now2 - lastProgramDateTimeMs <= maxManifestAgeMs;
11316
+ const priorEndSn = staleManifestEndSnRef.current;
11317
+ const isSequenceAdvanced = typeof lastSequenceNumber === "number" && (priorEndSn === null || lastSequenceNumber > priorEndSn);
11318
+ if (isFreshByProgramDateTime || isSequenceAdvanced) {
11319
+ stopStaleManifestPolling();
11320
+ if (hlsRef.current) {
11321
+ hlsRef.current.startLoad(-1);
11322
+ seekToLiveEdge();
11323
+ attemptPlay("stale manifest recovered");
11324
+ } else {
11325
+ setRestartKey((k) => k + 1);
11326
+ }
11327
+ return;
11328
+ }
11329
+ } catch (error) {
11330
+ debugLog("[HLS] Stale manifest poll failed", error);
11331
+ }
11332
+ staleManifestPollDelayRef.current = Math.min(
11333
+ staleManifestPollDelayRef.current + 5e3,
11334
+ STALE_MANIFEST_POLL_MAX_DELAY_MS
11335
+ );
11336
+ scheduleStaleManifestPoll();
11337
+ };
11338
+ const scheduleStaleManifestPoll = () => {
11339
+ if (!staleManifestTriggeredRef.current) return;
11340
+ if (staleManifestPollTimerRef.current) return;
11341
+ const delay2 = staleManifestPollDelayRef.current;
11342
+ staleManifestPollTimerRef.current = setTimeout(() => {
11343
+ staleManifestPollTimerRef.current = null;
11344
+ void pollStaleManifestOnce();
11345
+ }, delay2);
11346
+ };
11347
+ const startStaleManifestPolling = (reason) => {
11348
+ if (staleManifestTriggeredRef.current) return;
11349
+ staleManifestTriggeredRef.current = true;
11350
+ staleManifestUrlRef.current = activeStreamUrlRef.current || latestSrcRef.current;
11351
+ staleManifestEndSnRef.current = lastManifestEndSnRef.current;
11352
+ staleManifestPollDelayRef.current = STALE_MANIFEST_POLL_INITIAL_DELAY_MS;
11353
+ const hls = hlsRef.current;
11354
+ if (hls) {
11355
+ hls.stopLoad();
11356
+ }
11357
+ const video = videoRef.current;
11358
+ if (video) {
11359
+ video.pause();
11360
+ }
11361
+ console.warn(`[HLS] Manifest stale, pausing playback (${reason})`);
11362
+ scheduleStaleManifestPoll();
11363
+ };
11364
+ const isProxyUrl = (url) => {
11365
+ if (!proxyEnabled || !proxyBaseUrl) return false;
11366
+ try {
11367
+ const base = proxyBaseUrl.startsWith("http") ? proxyBaseUrl : new URL(proxyBaseUrl, window.location.origin).toString();
11368
+ return url.startsWith(base);
11369
+ } catch {
11370
+ return url.includes(proxyBaseUrl);
11371
+ }
11372
+ };
11373
+ const getR2CameraUuid = (url) => {
11374
+ try {
11375
+ const parsed = new URL(url);
11376
+ const match2 = parsed.pathname.match(/\/segments\/([^\/]+)\/index\.m3u8$/);
11377
+ if (match2) {
11378
+ return match2[1];
11379
+ }
11380
+ } catch {
11381
+ }
11382
+ const match = url.match(/\/segments\/([^\/]+)\/index\.m3u8/);
11383
+ return match ? match[1] : null;
11384
+ };
10966
11385
  const cleanup = () => {
10967
11386
  if (stallCheckIntervalRef.current) {
10968
11387
  clearInterval(stallCheckIntervalRef.current);
@@ -10972,10 +11391,48 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
10972
11391
  clearTimeout(noProgressTimerRef.current);
10973
11392
  noProgressTimerRef.current = null;
10974
11393
  }
11394
+ if (playbackRateIntervalRef.current) {
11395
+ clearInterval(playbackRateIntervalRef.current);
11396
+ playbackRateIntervalRef.current = null;
11397
+ }
10975
11398
  if (waitingTimerRef.current) {
10976
11399
  clearTimeout(waitingTimerRef.current);
10977
11400
  waitingTimerRef.current = null;
10978
11401
  }
11402
+ if (playRetryTimerRef.current) {
11403
+ clearTimeout(playRetryTimerRef.current);
11404
+ playRetryTimerRef.current = null;
11405
+ }
11406
+ if (manifestWatchdogRef.current) {
11407
+ clearInterval(manifestWatchdogRef.current);
11408
+ manifestWatchdogRef.current = null;
11409
+ }
11410
+ if (manifestRetryTimerRef.current) {
11411
+ clearTimeout(manifestRetryTimerRef.current);
11412
+ manifestRetryTimerRef.current = null;
11413
+ }
11414
+ stopStaleManifestPolling();
11415
+ lastHiddenAtRef.current = null;
11416
+ if (nativeStreamUrlRef.current) {
11417
+ nativeStreamUrlRef.current = null;
11418
+ }
11419
+ activeStreamUrlRef.current = null;
11420
+ forcedLiveStartRef.current = false;
11421
+ lastLiveReloadRef.current = 0;
11422
+ targetDurationRef.current = null;
11423
+ lastManifestLoadRef.current = 0;
11424
+ lastFragLoadRef.current = 0;
11425
+ lastFragSnRef.current = null;
11426
+ lastWindowStartSnRef.current = null;
11427
+ lastWindowEndSnRef.current = null;
11428
+ lastFragTimeoutSnRef.current = null;
11429
+ lastFragTimeoutAtRef.current = null;
11430
+ fragTimeoutCountRef.current = 0;
11431
+ lastManifestEndSnRef.current = null;
11432
+ lastManifestEndSnUpdatedAtRef.current = null;
11433
+ staleManifestTriggeredRef.current = false;
11434
+ manifestRetryDelayRef.current = 5e3;
11435
+ playRetryCountRef.current = 0;
10979
11436
  if (hlsRef.current) {
10980
11437
  hlsRef.current.destroy();
10981
11438
  hlsRef.current = null;
@@ -10987,11 +11444,24 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
10987
11444
  video.load();
10988
11445
  video.removeEventListener("waiting", handleWaiting);
10989
11446
  video.removeEventListener("timeupdate", handleTimeUpdate);
11447
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
11448
+ video.removeEventListener("canplay", handleCanPlay);
11449
+ video.removeEventListener("ended", handleEnded);
10990
11450
  video.removeEventListener("error", handleNativeError);
11451
+ if (typeof document !== "undefined") {
11452
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
11453
+ }
10991
11454
  }
10992
11455
  lastTimeUpdateRef.current = 0;
10993
11456
  softRestartCountRef.current = 0;
10994
11457
  };
11458
+ const getLiveOffsetSeconds = () => {
11459
+ const targetDuration = targetDurationRef.current;
11460
+ if (targetDuration && Number.isFinite(targetDuration)) {
11461
+ return Math.max(targetDuration * 2, targetDuration);
11462
+ }
11463
+ return DEFAULT_LIVE_OFFSET_SECONDS;
11464
+ };
10995
11465
  const seekToLiveEdge = () => {
10996
11466
  const hls = hlsRef.current;
10997
11467
  const video = videoRef.current;
@@ -11000,19 +11470,237 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
11000
11470
  video.currentTime = hls.liveSyncPosition;
11001
11471
  } else if (hls.levels?.[hls.currentLevel]?.details) {
11002
11472
  const levelDetails = hls.levels[hls.currentLevel].details;
11003
- if (levelDetails && levelDetails.edge !== void 0) {
11004
- video.currentTime = Math.max(0, levelDetails.edge - 5);
11473
+ const edge = levelDetails?.edge;
11474
+ if (edge !== void 0 && edge !== null) {
11475
+ const offset = getLiveOffsetSeconds();
11476
+ video.currentTime = Math.max(0, edge - offset);
11477
+ }
11478
+ }
11479
+ };
11480
+ const getLiveEdgeTime = () => {
11481
+ const video = videoRef.current;
11482
+ if (!video || video.seekable.length === 0) return null;
11483
+ const end = video.seekable.end(video.seekable.length - 1);
11484
+ if (!Number.isFinite(end)) return null;
11485
+ const offset = getLiveOffsetSeconds();
11486
+ return Math.max(0, end - offset);
11487
+ };
11488
+ const getWaitingTimeoutMs = () => {
11489
+ const targetDuration = targetDurationRef.current;
11490
+ if (targetDuration && Number.isFinite(targetDuration)) {
11491
+ return Math.max(2e4, targetDuration * 1e3 * 0.5);
11492
+ }
11493
+ return 3e4;
11494
+ };
11495
+ const getManifestStaleTimeoutMs = () => {
11496
+ const targetDuration = targetDurationRef.current;
11497
+ if (targetDuration && Number.isFinite(targetDuration)) {
11498
+ return Math.max(targetDuration * 2e3, 12e4);
11499
+ }
11500
+ return 12e4;
11501
+ };
11502
+ const isManifestUrl = (url) => url.includes(".m3u8");
11503
+ const getBufferGap = (video) => {
11504
+ if (!video.buffered.length) return null;
11505
+ const end = video.buffered.end(video.buffered.length - 1);
11506
+ if (!Number.isFinite(end)) return null;
11507
+ return Math.max(0, end - video.currentTime);
11508
+ };
11509
+ const getBufferThresholds = () => {
11510
+ const targetDuration = targetDurationRef.current ?? 60;
11511
+ return {
11512
+ low: Math.max(5, targetDuration * 0.1),
11513
+ mid: Math.max(10, targetDuration * 0.2),
11514
+ high: Math.max(18, targetDuration * 0.35)
11515
+ };
11516
+ };
11517
+ const shouldAttemptRecovery = () => {
11518
+ if (!isR2StreamRef.current) return true;
11519
+ if (!lastManifestLoadRef.current) return false;
11520
+ const now2 = Date.now();
11521
+ const timeoutMs = getManifestStaleTimeoutMs();
11522
+ const manifestAge = now2 - lastManifestLoadRef.current;
11523
+ return manifestAge > timeoutMs;
11524
+ };
11525
+ const getDesiredLivePosition = () => {
11526
+ const hls = hlsRef.current;
11527
+ if (hls?.liveSyncPosition !== null && hls?.liveSyncPosition !== void 0) {
11528
+ if (Number.isFinite(hls.liveSyncPosition)) {
11529
+ return hls.liveSyncPosition;
11530
+ }
11531
+ }
11532
+ if (hls?.levels?.[hls.currentLevel]?.details) {
11533
+ const levelDetails = hls.levels[hls.currentLevel].details;
11534
+ const edge = levelDetails?.edge;
11535
+ if (edge !== void 0 && edge !== null) {
11536
+ return Math.max(0, edge - getLiveOffsetSeconds());
11537
+ }
11538
+ }
11539
+ return getLiveEdgeTime();
11540
+ };
11541
+ const schedulePlayRetry = (reason) => {
11542
+ if (playRetryTimerRef.current) return;
11543
+ if (playRetryCountRef.current >= 3) return;
11544
+ playRetryCountRef.current += 1;
11545
+ playRetryTimerRef.current = setTimeout(() => {
11546
+ playRetryTimerRef.current = null;
11547
+ attemptPlay();
11548
+ }, 1e3);
11549
+ };
11550
+ const attemptPlay = (reason) => {
11551
+ const video = videoRef.current;
11552
+ if (!video || !shouldPlayRef.current) return;
11553
+ if (!video.paused || video.seeking) return;
11554
+ if (video.readyState < 2) return;
11555
+ video.play().then(() => {
11556
+ playRetryCountRef.current = 0;
11557
+ }).catch((err) => {
11558
+ if (err?.name === "AbortError") {
11559
+ schedulePlayRetry();
11560
+ return;
11005
11561
  }
11562
+ console.error("[HLS] Play failed:", err);
11563
+ });
11564
+ };
11565
+ const handleCanPlay = () => {
11566
+ attemptPlay();
11567
+ };
11568
+ const handleVisibilityChange = () => {
11569
+ if (typeof document === "undefined") return;
11570
+ if (document.hidden) {
11571
+ lastHiddenAtRef.current = Date.now();
11572
+ return;
11573
+ }
11574
+ const lastHiddenAt = lastHiddenAtRef.current;
11575
+ lastHiddenAtRef.current = null;
11576
+ if (!lastHiddenAt) return;
11577
+ if (Date.now() - lastHiddenAt < 3e4) return;
11578
+ refreshLiveStream("tab visible after idle");
11579
+ attemptPlay();
11580
+ };
11581
+ const startManifestWatchdog = () => {
11582
+ if (manifestWatchdogRef.current) return;
11583
+ if (!isR2StreamRef.current) return;
11584
+ manifestWatchdogRef.current = setInterval(() => {
11585
+ if (staleManifestTriggeredRef.current) return;
11586
+ if (!lastManifestLoadRef.current) return;
11587
+ const now2 = Date.now();
11588
+ const staleTimeoutMs = getManifestStaleTimeoutMs();
11589
+ if (now2 - lastManifestLoadRef.current < staleTimeoutMs) return;
11590
+ if (now2 - lastLiveReloadRef.current < LIVE_RELOAD_MIN_INTERVAL_MS) return;
11591
+ lastLiveReloadRef.current = now2;
11592
+ softRestart("manifest stale watchdog");
11593
+ }, 15e3);
11594
+ };
11595
+ const scheduleManifestRetry = (reason) => {
11596
+ if (manifestRetryTimerRef.current) return;
11597
+ const delay2 = manifestRetryDelayRef.current;
11598
+ manifestRetryTimerRef.current = setTimeout(() => {
11599
+ manifestRetryTimerRef.current = null;
11600
+ softRestart(reason);
11601
+ manifestRetryDelayRef.current = Math.min(manifestRetryDelayRef.current + 5e3, 3e4);
11602
+ }, delay2);
11603
+ };
11604
+ const resetManifestRetry = () => {
11605
+ if (manifestRetryTimerRef.current) {
11606
+ clearTimeout(manifestRetryTimerRef.current);
11607
+ manifestRetryTimerRef.current = null;
11608
+ }
11609
+ manifestRetryDelayRef.current = 5e3;
11610
+ };
11611
+ const markStaleStream = (reason) => {
11612
+ startStaleManifestPolling(reason);
11613
+ };
11614
+ const setPlaybackRate = (rate) => {
11615
+ const video = videoRef.current;
11616
+ if (!video) return;
11617
+ if (!Number.isFinite(rate)) return;
11618
+ if (Math.abs(video.playbackRate - rate) < 0.01) return;
11619
+ video.playbackRate = rate;
11620
+ };
11621
+ const startPlaybackGovernor = () => {
11622
+ if (playbackRateIntervalRef.current) return;
11623
+ playbackRateIntervalRef.current = setInterval(() => {
11624
+ const video = videoRef.current;
11625
+ if (!video || video.paused || video.seeking) return;
11626
+ if (!isR2StreamRef.current) return;
11627
+ const bufferGap = getBufferGap(video);
11628
+ if (bufferGap === null) return;
11629
+ const { low, mid, high } = getBufferThresholds();
11630
+ let desiredRate = 1;
11631
+ if (bufferGap < low) {
11632
+ desiredRate = 0.8;
11633
+ } else if (bufferGap < mid) {
11634
+ desiredRate = 0.9;
11635
+ } else if (bufferGap < high) {
11636
+ desiredRate = 0.95;
11637
+ }
11638
+ setPlaybackRate(desiredRate);
11639
+ }, 2e3);
11640
+ };
11641
+ const buildCacheBustedUrl = (url) => {
11642
+ if (typeof window === "undefined") {
11643
+ return url;
11644
+ }
11645
+ try {
11646
+ const parsed = new URL(url, window.location.origin);
11647
+ parsed.searchParams.set("ts", Date.now().toString());
11648
+ return parsed.toString();
11649
+ } catch {
11650
+ const separator = url.includes("?") ? "&" : "?";
11651
+ return `${url}${separator}ts=${Date.now()}`;
11652
+ }
11653
+ };
11654
+ const refreshLiveStream = (reason) => {
11655
+ if (!isR2StreamRef.current) return;
11656
+ const now2 = Date.now();
11657
+ if (now2 - lastLiveReloadRef.current < LIVE_RELOAD_MIN_INTERVAL_MS) {
11658
+ return;
11659
+ }
11660
+ lastLiveReloadRef.current = now2;
11661
+ const video = videoRef.current;
11662
+ const hls = hlsRef.current;
11663
+ if (hls) {
11664
+ console.log(`[HLS] Live reload (${reason})`);
11665
+ softRestart(reason);
11666
+ return;
11667
+ }
11668
+ if (video && nativeStreamUrlRef.current) {
11669
+ console.log(`[HLS] Native live reload (${reason})`);
11670
+ const refreshedUrl = buildCacheBustedUrl(nativeStreamUrlRef.current);
11671
+ video.src = refreshedUrl;
11672
+ video.load();
11673
+ const edgeTime = getLiveEdgeTime();
11674
+ if (edgeTime !== null) {
11675
+ video.currentTime = edgeTime;
11676
+ }
11677
+ attemptPlay();
11006
11678
  }
11007
11679
  };
11008
11680
  const softRestart = (reason) => {
11681
+ if (staleManifestTriggeredRef.current) {
11682
+ debugLog("[HLS] Skip soft restart while manifest is stale", reason);
11683
+ return;
11684
+ }
11009
11685
  console.warn(`[HLS] Soft restart: ${reason}`);
11010
11686
  const hls = hlsRef.current;
11011
11687
  if (!hls) return;
11012
11688
  try {
11689
+ const video = videoRef.current;
11690
+ const isR2Stream = isR2StreamRef.current;
11691
+ const desiredPosition = isR2Stream ? getDesiredLivePosition() : null;
11013
11692
  hls.stopLoad();
11014
- hls.startLoad(-1);
11015
- seekToLiveEdge();
11693
+ if (desiredPosition !== null && Number.isFinite(desiredPosition)) {
11694
+ hls.startLoad(desiredPosition);
11695
+ if (video) {
11696
+ video.currentTime = desiredPosition;
11697
+ }
11698
+ } else {
11699
+ hls.startLoad(-1);
11700
+ if (!isR2Stream) {
11701
+ seekToLiveEdge();
11702
+ }
11703
+ }
11016
11704
  softRestartCountRef.current++;
11017
11705
  if (softRestartCountRef.current >= 5) {
11018
11706
  hardRestart(`${reason} (escalated after ${softRestartCountRef.current} soft restarts)`);
@@ -11023,11 +11711,16 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
11023
11711
  }
11024
11712
  };
11025
11713
  const hardRestart = (reason) => {
11714
+ if (staleManifestTriggeredRef.current) {
11715
+ debugLog("[HLS] Skip hard restart while manifest is stale", reason);
11716
+ return;
11717
+ }
11026
11718
  console.warn(`[HLS] Hard restart: ${reason}`);
11027
11719
  cleanupFailedUrls();
11028
11720
  const failure = failedUrls.get(src);
11029
11721
  const failureCount = failure ? failure.count : 0;
11030
- if (failureCount >= MAX_FAILURES_PER_URL) {
11722
+ const isR2Stream = isR2StreamRef.current;
11723
+ if (!isR2Stream && failureCount >= MAX_FAILURES_PER_URL) {
11031
11724
  console.error(`[HLS] URL has failed ${failureCount} times. Marking as permanently failed: ${src}`);
11032
11725
  failedUrls.set(src, {
11033
11726
  count: failureCount,
@@ -11040,11 +11733,15 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
11040
11733
  }
11041
11734
  return;
11042
11735
  }
11043
- failedUrls.set(src, {
11044
- count: failureCount + 1,
11045
- timestamp: Date.now(),
11046
- permanentlyFailed: false
11047
- });
11736
+ if (!isR2Stream) {
11737
+ failedUrls.set(src, {
11738
+ count: failureCount + 1,
11739
+ timestamp: Date.now(),
11740
+ permanentlyFailed: false
11741
+ });
11742
+ } else if (failedUrls.has(src)) {
11743
+ failedUrls.delete(src);
11744
+ }
11048
11745
  cleanup();
11049
11746
  setRestartKey((k) => k + 1);
11050
11747
  softRestartCountRef.current = 0;
@@ -11054,12 +11751,49 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
11054
11751
  }
11055
11752
  }
11056
11753
  };
11754
+ const handleLoadedMetadata = () => {
11755
+ if (!isR2StreamRef.current) return;
11756
+ const video = videoRef.current;
11757
+ if (!video) return;
11758
+ const edgeTime = getLiveEdgeTime();
11759
+ if (edgeTime !== null) {
11760
+ video.currentTime = edgeTime;
11761
+ }
11762
+ };
11763
+ const handleEnded = () => {
11764
+ if (isNativeHlsRef.current) {
11765
+ refreshLiveStream("ended");
11766
+ return;
11767
+ }
11768
+ if (isR2StreamRef.current) {
11769
+ softRestart("ended");
11770
+ }
11771
+ };
11057
11772
  const handleWaiting = () => {
11058
- if (isNativeHlsRef.current) return;
11059
- console.log("[HLS] Video waiting (buffer underrun)");
11060
11773
  if (waitingTimerRef.current) {
11061
11774
  clearTimeout(waitingTimerRef.current);
11062
11775
  }
11776
+ if (isNativeHlsRef.current) {
11777
+ if (!isR2StreamRef.current) return;
11778
+ waitingTimerRef.current = setTimeout(() => {
11779
+ refreshLiveStream("native waiting timeout");
11780
+ }, getWaitingTimeoutMs());
11781
+ return;
11782
+ }
11783
+ console.log("[HLS] Video waiting (buffer underrun)");
11784
+ if (isR2StreamRef.current) {
11785
+ waitingTimerRef.current = setTimeout(() => {
11786
+ const video = videoRef.current;
11787
+ if (video && video.readyState < 3) {
11788
+ if (shouldAttemptRecovery()) {
11789
+ softRestart("waiting timeout");
11790
+ } else {
11791
+ handleWaiting();
11792
+ }
11793
+ }
11794
+ }, getWaitingTimeoutMs());
11795
+ return;
11796
+ }
11063
11797
  waitingTimerRef.current = setTimeout(() => {
11064
11798
  const video = videoRef.current;
11065
11799
  if (video && video.readyState < 3) {
@@ -11071,6 +11805,7 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
11071
11805
  const video = videoRef.current;
11072
11806
  if (!video) return;
11073
11807
  lastTimeUpdateRef.current = video.currentTime;
11808
+ playRetryCountRef.current = 0;
11074
11809
  if (waitingTimerRef.current) {
11075
11810
  clearTimeout(waitingTimerRef.current);
11076
11811
  waitingTimerRef.current = null;
@@ -11081,19 +11816,33 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
11081
11816
  hardRestart("native video error");
11082
11817
  };
11083
11818
  const startStallDetection = () => {
11084
- if (isNativeHlsRef.current) return;
11819
+ if (isNativeHlsRef.current || isR2StreamRef.current) return;
11085
11820
  stallCheckIntervalRef.current = setInterval(() => {
11086
11821
  const video = videoRef.current;
11087
11822
  if (!video || video.paused || video.ended) return;
11823
+ if (video.readyState < 3 || video.seeking) return;
11088
11824
  const currentTime = video.currentTime;
11089
11825
  const lastTime = lastTimeUpdateRef.current;
11826
+ const bufferGap = getBufferGap(video);
11827
+ const nearBufferEnd = bufferGap !== null && bufferGap < 0.5;
11090
11828
  if (Math.abs(currentTime - lastTime) < 0.1 && video.readyState >= 2) {
11829
+ if (nearBufferEnd && !shouldAttemptRecovery()) {
11830
+ if (noProgressTimerRef.current) {
11831
+ clearTimeout(noProgressTimerRef.current);
11832
+ noProgressTimerRef.current = null;
11833
+ }
11834
+ return;
11835
+ }
11091
11836
  console.warn("[HLS] Playback stall detected");
11092
11837
  if (!noProgressTimerRef.current) {
11093
11838
  noProgressTimerRef.current = setTimeout(() => {
11839
+ if (nearBufferEnd && !shouldAttemptRecovery()) {
11840
+ noProgressTimerRef.current = null;
11841
+ return;
11842
+ }
11094
11843
  softRestart("playback stall");
11095
11844
  noProgressTimerRef.current = null;
11096
- }, 8e3);
11845
+ }, nearBufferEnd ? getWaitingTimeoutMs() : 12e3);
11097
11846
  }
11098
11847
  } else {
11099
11848
  if (noProgressTimerRef.current) {
@@ -11104,65 +11853,311 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
11104
11853
  }, 7e3);
11105
11854
  };
11106
11855
  useEffect(() => {
11107
- if (isPermanentlyFailed) {
11856
+ if (!src || !shouldPlay) {
11857
+ cleanup();
11858
+ return;
11859
+ }
11860
+ let isCancelled = false;
11861
+ const r2WorkerDomain = process.env.NEXT_PUBLIC_R2_WORKER_DOMAIN || "https://r2-stream-proxy.optifye-r2.workers.dev";
11862
+ const isR2Stream = isR2WorkerUrl(src, r2WorkerDomain);
11863
+ isR2StreamRef.current = isR2Stream;
11864
+ if (isPermanentlyFailed && !isR2Stream) {
11108
11865
  console.warn(`[HLS] URL is permanently failed, not attempting to load: ${src}`);
11109
11866
  if (onFatalError) {
11110
11867
  onFatalError();
11111
11868
  }
11112
11869
  return;
11113
11870
  }
11114
- if (!src || !shouldPlay) {
11115
- cleanup();
11116
- return;
11871
+ if (isPermanentlyFailed && isR2Stream && failedUrls.has(src)) {
11872
+ failedUrls.delete(src);
11117
11873
  }
11118
11874
  const video = videoRef.current;
11119
11875
  if (!video) return;
11120
- isNativeHlsRef.current = video.canPlayType("application/vnd.apple.mpegurl") === "probably";
11121
- if (Hls3.isSupported() && !isNativeHlsRef.current) {
11122
- const hls = new Hls3(HLS_CONFIG);
11123
- hlsRef.current = hls;
11124
- hls.attachMedia(video);
11125
- hls.loadSource(src);
11126
- hls.on(Hls3.Events.ERROR, (_, data) => {
11127
- if (!data.fatal) return;
11128
- console.error("[HLS] Fatal error:", data.type, data.details);
11129
- if (data.response?.code === 404) {
11130
- hardRestart("404 hard restart");
11876
+ if (typeof document !== "undefined") {
11877
+ document.addEventListener("visibilitychange", handleVisibilityChange);
11878
+ }
11879
+ const initialize = async () => {
11880
+ const canUseNative = video.canPlayType("application/vnd.apple.mpegurl") === "probably";
11881
+ isNativeHlsRef.current = canUseNative && !isR2Stream;
11882
+ let authToken = null;
11883
+ if (isR2Stream) {
11884
+ try {
11885
+ const supabase = _getSupabaseInstance();
11886
+ authToken = await getAuthTokenForHls(supabase);
11887
+ } catch (error) {
11888
+ console.warn("[HLS] Unable to retrieve auth token for R2 streaming:", error);
11889
+ }
11890
+ }
11891
+ authTokenRef.current = authToken;
11892
+ if (isCancelled) return;
11893
+ const cameraUuid = isR2Stream ? getR2CameraUuid(src) : null;
11894
+ const proxyPlaylistUrl = proxyEnabled && isR2Stream && proxyBaseUrl && cameraUuid ? `${proxyBaseUrl}/segments/${cameraUuid}/index.m3u8` : null;
11895
+ const resolvedSrc = proxyPlaylistUrl || src;
11896
+ const resolvedHlsSrc = src;
11897
+ const shouldUseProxy = proxyEnabled && isR2Stream && canUseNative && !Hls.isSupported() && isSafari();
11898
+ if (shouldUseProxy) {
11899
+ if (cameraUuid) {
11900
+ isNativeHlsRef.current = true;
11901
+ nativeStreamUrlRef.current = resolvedSrc;
11902
+ activeStreamUrlRef.current = resolvedSrc;
11903
+ console.log("[HLS] Using proxy playlist for Safari R2 stream");
11904
+ video.src = resolvedSrc;
11905
+ video.addEventListener("waiting", handleWaiting);
11906
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
11907
+ video.addEventListener("canplay", handleCanPlay);
11908
+ video.addEventListener("ended", handleEnded);
11909
+ video.addEventListener("error", handleNativeError);
11910
+ attemptPlay();
11131
11911
  return;
11132
11912
  }
11133
- switch (data.type) {
11134
- case Hls3.ErrorTypes.NETWORK_ERROR:
11135
- case Hls3.ErrorTypes.MEDIA_ERROR:
11136
- softRestart(`${data.type}: ${data.details}`);
11137
- break;
11138
- default:
11139
- hardRestart(`Fatal ${data.type}: ${data.details}`);
11140
- break;
11141
- }
11142
- });
11143
- hls.on(Hls3.Events.MANIFEST_PARSED, () => {
11144
- if (failedUrls.has(src)) {
11145
- console.log(`[HLS] Stream loaded successfully, resetting failure count for: ${src}`);
11146
- failedUrls.delete(src);
11913
+ console.warn("[HLS] Safari R2 proxy unavailable, falling back to direct URL");
11914
+ }
11915
+ if (!proxyEnabled && isR2Stream && canUseNative && !Hls.isSupported() && isSafari()) {
11916
+ console.warn("[HLS] Safari native R2 streaming requires proxy. Falling back.");
11917
+ if (onFatalError) {
11918
+ onFatalError();
11147
11919
  }
11148
- video.play().catch((err) => {
11149
- console.error("[HLS] Play failed:", err);
11920
+ return;
11921
+ }
11922
+ if (Hls.isSupported() && !isNativeHlsRef.current) {
11923
+ const mergedConfig = {
11924
+ ...HLS_CONFIG,
11925
+ ...hlsConfig,
11926
+ xhrSetup: (xhr, url) => {
11927
+ const usesProxy = isProxyUrl(url);
11928
+ if (isR2WorkerUrl(url, r2WorkerDomain) || usesProxy) {
11929
+ if (isManifestUrl(url)) {
11930
+ xhr.open("GET", buildCacheBustedUrl(url), true);
11931
+ }
11932
+ if (authToken) {
11933
+ xhr.setRequestHeader("Authorization", `Bearer ${authToken}`);
11934
+ }
11935
+ }
11936
+ },
11937
+ fetchSetup: (context, initParams) => {
11938
+ const isR2Url = isR2WorkerUrl(context.url, r2WorkerDomain);
11939
+ const isManifestRequest = isManifestUrl(context.url);
11940
+ const usesProxy = isProxyUrl(context.url);
11941
+ let requestUrl = context.url;
11942
+ if (isR2Url || usesProxy) {
11943
+ if (authToken) {
11944
+ initParams.headers = {
11945
+ ...initParams.headers,
11946
+ "Authorization": `Bearer ${authToken}`
11947
+ };
11948
+ }
11949
+ if (isManifestRequest) {
11950
+ requestUrl = buildCacheBustedUrl(context.url);
11951
+ initParams.cache = "no-store";
11952
+ }
11953
+ }
11954
+ return new Request(requestUrl, initParams);
11955
+ }
11956
+ };
11957
+ const hls = new Hls(mergedConfig);
11958
+ hlsRef.current = hls;
11959
+ hls.attachMedia(video);
11960
+ hls.loadSource(resolvedHlsSrc);
11961
+ activeStreamUrlRef.current = resolvedHlsSrc;
11962
+ hls.on(Hls.Events.ERROR, (_, data) => {
11963
+ debugLog("[HLS] Error", {
11964
+ type: data.type,
11965
+ details: data.details,
11966
+ fatal: data.fatal,
11967
+ response: data.response?.code,
11968
+ frag: data.frag?.sn
11969
+ });
11970
+ if (data.type === Hls.ErrorTypes.MEDIA_ERROR && data.details === Hls.ErrorDetails.BUFFER_STALLED_ERROR) {
11971
+ debugLog("[HLS] Buffer stalled, waiting for next segment");
11972
+ attemptPlay();
11973
+ return;
11974
+ }
11975
+ if (data.type === Hls.ErrorTypes.NETWORK_ERROR && (data.details === Hls.ErrorDetails.FRAG_LOAD_TIMEOUT || data.details === Hls.ErrorDetails.FRAG_LOAD_ERROR)) {
11976
+ const fragSn = data.frag?.sn;
11977
+ const windowStart = lastWindowStartSnRef.current;
11978
+ const windowEnd = lastWindowEndSnRef.current;
11979
+ if (typeof fragSn === "number" && typeof windowStart === "number" && fragSn < windowStart) {
11980
+ softRestart("frag fell out of window");
11981
+ return;
11982
+ }
11983
+ if (typeof fragSn === "number" && typeof windowEnd === "number" && fragSn > windowEnd) {
11984
+ softRestart("frag ahead of window");
11985
+ return;
11986
+ }
11987
+ const now2 = Date.now();
11988
+ if (lastFragTimeoutSnRef.current === fragSn && lastFragTimeoutAtRef.current) {
11989
+ if (now2 - lastFragTimeoutAtRef.current < 12e4) {
11990
+ fragTimeoutCountRef.current += 1;
11991
+ } else {
11992
+ fragTimeoutCountRef.current = 1;
11993
+ }
11994
+ } else {
11995
+ fragTimeoutCountRef.current = 1;
11996
+ lastFragTimeoutSnRef.current = fragSn ?? null;
11997
+ }
11998
+ lastFragTimeoutAtRef.current = now2;
11999
+ if (fragTimeoutCountRef.current >= 2) {
12000
+ softRestart("frag load timeout");
12001
+ }
12002
+ return;
12003
+ }
12004
+ if (data.type === Hls.ErrorTypes.NETWORK_ERROR && (data.details === Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT || data.details === Hls.ErrorDetails.MANIFEST_LOAD_ERROR)) {
12005
+ scheduleManifestRetry("manifest load timeout");
12006
+ return;
12007
+ }
12008
+ if (!data.fatal) return;
12009
+ console.error("[HLS] Fatal error:", data.type, data.details);
12010
+ if (data.response?.code === 404) {
12011
+ if (data.details === Hls.ErrorDetails.MANIFEST_LOAD_ERROR || data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR) {
12012
+ if (isR2StreamRef.current) {
12013
+ scheduleManifestRetry("manifest 404");
12014
+ return;
12015
+ }
12016
+ hardRestart("404 manifest hard restart");
12017
+ return;
12018
+ }
12019
+ softRestart("frag 404");
12020
+ return;
12021
+ }
12022
+ switch (data.type) {
12023
+ case Hls.ErrorTypes.NETWORK_ERROR:
12024
+ if (isR2StreamRef.current && data.details === Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT) {
12025
+ softRestart("manifest load timeout");
12026
+ break;
12027
+ }
12028
+ softRestart(`${data.type}: ${data.details}`);
12029
+ break;
12030
+ case Hls.ErrorTypes.MEDIA_ERROR:
12031
+ softRestart(`${data.type}: ${data.details}`);
12032
+ break;
12033
+ default:
12034
+ hardRestart(`Fatal ${data.type}: ${data.details}`);
12035
+ break;
12036
+ }
11150
12037
  });
11151
- });
11152
- video.addEventListener("waiting", handleWaiting);
11153
- video.addEventListener("timeupdate", handleTimeUpdate);
11154
- startStallDetection();
11155
- } else if (isNativeHlsRef.current) {
11156
- console.log("[HLS] Using native HLS");
11157
- video.src = src;
11158
- video.addEventListener("error", handleNativeError);
11159
- video.play().catch((err) => {
11160
- console.error("[HLS] Native play failed:", err);
11161
- });
11162
- } else {
11163
- console.error("[HLS] HLS not supported");
11164
- }
11165
- return cleanup;
12038
+ hls.on(Hls.Events.MANIFEST_PARSED, () => {
12039
+ if (failedUrls.has(src)) {
12040
+ console.log(`[HLS] Stream loaded successfully, resetting failure count for: ${src}`);
12041
+ failedUrls.delete(src);
12042
+ }
12043
+ if (isR2Stream) {
12044
+ seekToLiveEdge();
12045
+ }
12046
+ attemptPlay();
12047
+ });
12048
+ hls.on(Hls.Events.LEVEL_LOADED, (_event, data) => {
12049
+ if (!data?.details) return;
12050
+ const details = data.details;
12051
+ if (isR2Stream) {
12052
+ lastManifestLoadRef.current = Date.now();
12053
+ resetManifestRetry();
12054
+ if (details.endList) {
12055
+ details.endList = false;
12056
+ }
12057
+ details.live = true;
12058
+ details.type = "LIVE";
12059
+ if (details.targetduration && Number.isFinite(details.targetduration)) {
12060
+ targetDurationRef.current = details.targetduration;
12061
+ }
12062
+ if (typeof details.startSN === "number") {
12063
+ lastWindowStartSnRef.current = details.startSN;
12064
+ }
12065
+ if (typeof details.endSN === "number") {
12066
+ lastWindowEndSnRef.current = Math.max(details.endSN - 1, details.startSN ?? details.endSN);
12067
+ }
12068
+ if (Array.isArray(details.fragments) && details.fragments.length > 0) {
12069
+ const firstSn = details.fragments[0]?.sn;
12070
+ const lastSn = details.fragments[details.fragments.length - 1]?.sn;
12071
+ if (typeof firstSn === "number") {
12072
+ lastWindowStartSnRef.current = firstSn;
12073
+ }
12074
+ if (typeof lastSn === "number") {
12075
+ lastWindowEndSnRef.current = lastSn;
12076
+ }
12077
+ }
12078
+ }
12079
+ if (maxManifestAgeMs > 0 && !details.endList) {
12080
+ const now2 = Date.now();
12081
+ const fragments = Array.isArray(details.fragments) ? details.fragments : [];
12082
+ const lastFragment = fragments.length ? fragments[fragments.length - 1] : void 0;
12083
+ const programDateTimeMs = getProgramDateTimeMs(lastFragment?.programDateTime);
12084
+ if (programDateTimeMs && now2 - programDateTimeMs > maxManifestAgeMs) {
12085
+ markStaleStream(`segment age ${Math.round((now2 - programDateTimeMs) / 1e3)}s`);
12086
+ return;
12087
+ }
12088
+ const endSn = typeof details.endSN === "number" ? details.endSN : lastFragment?.sn;
12089
+ if (typeof endSn === "number") {
12090
+ if (lastManifestEndSnRef.current === endSn) {
12091
+ const lastUpdatedAt = lastManifestEndSnUpdatedAtRef.current;
12092
+ if (lastUpdatedAt && now2 - lastUpdatedAt > maxManifestAgeMs) {
12093
+ markStaleStream(`sequence stalled at ${endSn}`);
12094
+ return;
12095
+ }
12096
+ } else {
12097
+ lastManifestEndSnRef.current = endSn;
12098
+ lastManifestEndSnUpdatedAtRef.current = now2;
12099
+ }
12100
+ }
12101
+ }
12102
+ debugLog("[HLS] Level loaded", {
12103
+ targetduration: details.targetduration,
12104
+ edge: details.edge,
12105
+ fragments: data.details?.fragments?.length
12106
+ });
12107
+ if (isR2Stream && !forcedLiveStartRef.current) {
12108
+ forcedLiveStartRef.current = true;
12109
+ seekToLiveEdge();
12110
+ }
12111
+ });
12112
+ hls.on(Hls.Events.MANIFEST_LOADED, () => {
12113
+ if (!isR2Stream) return;
12114
+ lastManifestLoadRef.current = Date.now();
12115
+ resetManifestRetry();
12116
+ });
12117
+ hls.on(Hls.Events.FRAG_LOADED, (_event, data) => {
12118
+ if (!isR2Stream) return;
12119
+ lastFragLoadRef.current = Date.now();
12120
+ if (typeof data?.frag?.sn === "number") {
12121
+ lastFragSnRef.current = data.frag.sn;
12122
+ }
12123
+ });
12124
+ hls.on(Hls.Events.FRAG_BUFFERED, () => {
12125
+ attemptPlay();
12126
+ });
12127
+ video.addEventListener("waiting", handleWaiting);
12128
+ video.addEventListener("timeupdate", handleTimeUpdate);
12129
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
12130
+ video.addEventListener("canplay", handleCanPlay);
12131
+ video.addEventListener("ended", handleEnded);
12132
+ startStallDetection();
12133
+ startPlaybackGovernor();
12134
+ startManifestWatchdog();
12135
+ startManifestWatchdog();
12136
+ return;
12137
+ }
12138
+ if (canUseNative) {
12139
+ isNativeHlsRef.current = true;
12140
+ console.log("[HLS] Using native HLS");
12141
+ video.src = resolvedSrc;
12142
+ nativeStreamUrlRef.current = resolvedSrc;
12143
+ activeStreamUrlRef.current = resolvedSrc;
12144
+ video.addEventListener("waiting", handleWaiting);
12145
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
12146
+ video.addEventListener("canplay", handleCanPlay);
12147
+ video.addEventListener("ended", handleEnded);
12148
+ video.addEventListener("error", handleNativeError);
12149
+ startPlaybackGovernor();
12150
+ startManifestWatchdog();
12151
+ attemptPlay();
12152
+ } else {
12153
+ console.error("[HLS] HLS not supported");
12154
+ }
12155
+ };
12156
+ initialize();
12157
+ return () => {
12158
+ isCancelled = true;
12159
+ cleanup();
12160
+ };
11166
12161
  }, [src, shouldPlay, restartKey, onFatalError, isPermanentlyFailed]);
11167
12162
  return {
11168
12163
  restartKey,
@@ -11170,11 +12165,11 @@ function useHlsStream(videoRef, { src, shouldPlay, onFatalError }) {
11170
12165
  };
11171
12166
  }
11172
12167
  function useHlsStreamWithCropping(videoRef, canvasRef, options) {
11173
- const { src, shouldPlay, cropping, canvasFps = 30, useRAF = true, onFatalError } = options;
12168
+ const { src, shouldPlay, cropping, canvasFps = 30, useRAF = true, onFatalError, hlsConfig } = options;
11174
12169
  const animationFrameRef = useRef(null);
11175
12170
  const intervalRef = useRef(null);
11176
12171
  const isDrawingRef = useRef(false);
11177
- const hlsState = useHlsStream(videoRef, { src, shouldPlay, onFatalError });
12172
+ const hlsState = useHlsStream(videoRef, { src, shouldPlay, onFatalError, hlsConfig });
11178
12173
  const calculateCropRect = useCallback((video, cropping2) => {
11179
12174
  const videoWidth = video.videoWidth;
11180
12175
  const videoHeight = video.videoHeight;
@@ -11908,6 +12903,95 @@ var useWorkspaceDisplayNamesMap = (workspaceIds, lineId, companyId) => {
11908
12903
  refetch: fetchDisplayNames
11909
12904
  };
11910
12905
  };
12906
+ var useWorkspaceVideoStreams = (workspaces) => {
12907
+ const workspaceIdsKey = useMemo(() => {
12908
+ const ids = /* @__PURE__ */ new Set();
12909
+ for (const workspace of workspaces) {
12910
+ if (workspace.workspace_uuid) {
12911
+ ids.add(workspace.workspace_uuid);
12912
+ }
12913
+ }
12914
+ return Array.from(ids).sort().join(",");
12915
+ }, [workspaces]);
12916
+ const lineIdsKey = useMemo(() => {
12917
+ const ids = /* @__PURE__ */ new Set();
12918
+ for (const workspace of workspaces) {
12919
+ if (workspace.line_id) {
12920
+ ids.add(workspace.line_id);
12921
+ }
12922
+ }
12923
+ return Array.from(ids).sort().join(",");
12924
+ }, [workspaces]);
12925
+ const requestKey = useMemo(() => {
12926
+ if (workspaceIdsKey) {
12927
+ return `workspaces:${workspaceIdsKey}`;
12928
+ }
12929
+ if (lineIdsKey) {
12930
+ return `lines:${lineIdsKey}`;
12931
+ }
12932
+ return "";
12933
+ }, [workspaceIdsKey, lineIdsKey]);
12934
+ const workspaceIds = useMemo(
12935
+ () => workspaceIdsKey ? workspaceIdsKey.split(",") : [],
12936
+ [workspaceIdsKey]
12937
+ );
12938
+ const lineIds = useMemo(
12939
+ () => lineIdsKey ? lineIdsKey.split(",") : [],
12940
+ [lineIdsKey]
12941
+ );
12942
+ const [state, setState] = useState(() => ({
12943
+ streamsByWorkspaceId: {},
12944
+ isLoading: Boolean(requestKey),
12945
+ error: null
12946
+ }));
12947
+ const lastKeyRef = useRef(requestKey);
12948
+ useEffect(() => {
12949
+ lastKeyRef.current = requestKey;
12950
+ if (!workspaceIds.length && !lineIds.length) {
12951
+ setState({
12952
+ streamsByWorkspaceId: {},
12953
+ isLoading: false,
12954
+ error: null
12955
+ });
12956
+ return;
12957
+ }
12958
+ let isCancelled = false;
12959
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
12960
+ const fetchStreams = async () => {
12961
+ try {
12962
+ const streams = await workspaceService.getWorkspaceVideoStreams({
12963
+ workspaceIds,
12964
+ lineIds
12965
+ });
12966
+ if (!isCancelled) {
12967
+ setState({
12968
+ streamsByWorkspaceId: streams,
12969
+ isLoading: false,
12970
+ error: null
12971
+ });
12972
+ }
12973
+ } catch (error) {
12974
+ if (!isCancelled) {
12975
+ setState((prev) => ({
12976
+ ...prev,
12977
+ isLoading: false,
12978
+ error: error instanceof Error ? error.message : "Failed to load video streams"
12979
+ }));
12980
+ }
12981
+ }
12982
+ };
12983
+ fetchStreams();
12984
+ return () => {
12985
+ isCancelled = true;
12986
+ };
12987
+ }, [workspaceIdsKey, lineIdsKey]);
12988
+ const isLoading = state.isLoading || lastKeyRef.current !== requestKey;
12989
+ return {
12990
+ streamsByWorkspaceId: state.streamsByWorkspaceId,
12991
+ isLoading,
12992
+ error: state.error
12993
+ };
12994
+ };
11911
12995
  var useActiveBreaks = (lineIds) => {
11912
12996
  const [activeBreaks, setActiveBreaks] = useState([]);
11913
12997
  const [isLoading, setIsLoading] = useState(true);
@@ -17440,24 +18524,6 @@ var createThrottledReload = (interval = 5e3, maxReloads = 3) => {
17440
18524
  };
17441
18525
  var throttledReloadDashboard = createThrottledReload(5e3, 3);
17442
18526
 
17443
- // src/lib/utils/browser.ts
17444
- function isSafari() {
17445
- if (typeof window === "undefined") return false;
17446
- const ua = window.navigator.userAgent;
17447
- const isSafariUA = /^((?!chrome|android).)*safari/i.test(ua);
17448
- const isIOS = /iPad|iPhone|iPod/.test(ua) && !window.MSStream;
17449
- return isSafariUA || isIOS;
17450
- }
17451
- function getBrowserName() {
17452
- if (typeof window === "undefined") return "server";
17453
- const ua = window.navigator.userAgent;
17454
- if (/chrome/i.test(ua) && !/edge/i.test(ua)) return "chrome";
17455
- if (/safari/i.test(ua) && !/chrome/i.test(ua)) return "safari";
17456
- if (/firefox/i.test(ua)) return "firefox";
17457
- if (/edge/i.test(ua)) return "edge";
17458
- return "unknown";
17459
- }
17460
-
17461
18527
  // src/lib/utils/index.ts
17462
18528
  var formatIdleTime = (idleTimeInSeconds) => {
17463
18529
  if (!idleTimeInSeconds || idleTimeInSeconds <= 0) {
@@ -17476,83 +18542,29 @@ var formatIdleTime = (idleTimeInSeconds) => {
17476
18542
  parts.push(`${seconds}s`);
17477
18543
  return parts.join(" ");
17478
18544
  };
17479
- var createSupabaseClient = (url, key) => createClient(url, key, {
17480
- auth: {
17481
- autoRefreshToken: true,
17482
- persistSession: true,
17483
- detectSessionInUrl: true,
17484
- flowType: "pkce",
17485
- // Enable debug logging in development
17486
- debug: process.env.NODE_ENV === "development"
17487
- },
17488
- db: {
17489
- schema: "public"
17490
- // Default schema, we'll use .schema('ai') in queries
17491
- },
17492
- global: {
17493
- headers: {
17494
- "x-application-name": "optifye-dashboard"
17495
- },
17496
- // Add global fetch timeout (5 minutes)
17497
- fetch: async (url2, options = {}) => {
17498
- const controller = new AbortController();
17499
- const timeoutId = setTimeout(() => controller.abort(), 3e5);
17500
- try {
17501
- const response = await fetch(url2, {
17502
- ...options,
17503
- signal: controller.signal
17504
- });
17505
- clearTimeout(timeoutId);
17506
- return response;
17507
- } catch (error) {
17508
- clearTimeout(timeoutId);
17509
- throw error;
17510
- }
17511
- }
18545
+ var cachedClient2 = null;
18546
+ var cachedConfig2 = null;
18547
+ var getCachedClient = (url, key) => {
18548
+ if (cachedClient2 && cachedConfig2?.url === url && cachedConfig2?.key === key) {
18549
+ return cachedClient2;
17512
18550
  }
17513
- });
17514
- var getAnonClient = () => {
17515
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
17516
- const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
17517
- if (!url || !key) {
17518
- console.warn("[dashboard-core] Missing supabase env variables");
17519
- }
17520
- return createClient(url, key, {
17521
- auth: {
17522
- autoRefreshToken: true,
17523
- persistSession: true,
17524
- detectSessionInUrl: true,
17525
- flowType: "pkce",
17526
- debug: process.env.NODE_ENV === "development"
17527
- },
17528
- global: {
17529
- headers: {
17530
- "x-application-name": "optifye-dashboard"
17531
- },
17532
- // Add global fetch timeout (5 minutes)
17533
- fetch: async (url2, options = {}) => {
17534
- const controller = new AbortController();
17535
- const timeoutId = setTimeout(() => controller.abort(), 3e5);
17536
- try {
17537
- const response = await fetch(url2, {
17538
- ...options,
17539
- signal: controller.signal
17540
- });
17541
- clearTimeout(timeoutId);
17542
- return response;
17543
- } catch (error) {
17544
- clearTimeout(timeoutId);
17545
- throw error;
17546
- }
17547
- }
17548
- }
17549
- });
18551
+ cachedClient2 = createSupabaseClient(url, key);
18552
+ cachedConfig2 = { url, key };
18553
+ _setSupabaseInstance(cachedClient2);
18554
+ return cachedClient2;
17550
18555
  };
17551
-
17552
- // src/lib/supabaseClient.ts
17553
18556
  function useSupabaseClient() {
17554
18557
  const { supabaseUrl, supabaseKey } = useDashboardConfig();
17555
- const supabase = useMemo(() => createSupabaseClient(supabaseUrl, supabaseKey), [supabaseUrl, supabaseKey]);
18558
+ const providerClient = useOptionalSupabase();
18559
+ const supabase = useMemo(() => {
18560
+ if (providerClient) {
18561
+ return providerClient;
18562
+ }
18563
+ if (!supabaseUrl || !supabaseKey) {
18564
+ throw new Error("Supabase configuration missing in DashboardConfig");
18565
+ }
18566
+ return getCachedClient(supabaseUrl, supabaseKey);
18567
+ }, [providerClient, supabaseUrl, supabaseKey]);
17556
18568
  return supabase;
17557
18569
  }
17558
18570
  var LayoutGroupContext = createContext({});
@@ -27633,22 +28645,14 @@ var VideoCard = React25__default.memo(({
27633
28645
  const videoRef = useRef(null);
27634
28646
  const canvasRef = useRef(null);
27635
28647
  const effectiveLegend = legend || DEFAULT_EFFICIENCY_LEGEND;
27636
- if (cropping) {
27637
- useHlsStreamWithCropping(videoRef, canvasRef, {
27638
- src: hlsUrl,
27639
- shouldPlay,
27640
- cropping,
27641
- canvasFps,
27642
- useRAF,
27643
- onFatalError: onFatalError ?? (() => throttledReloadDashboard())
27644
- });
27645
- } else {
27646
- useHlsStream(videoRef, {
27647
- src: hlsUrl,
27648
- shouldPlay,
27649
- onFatalError: onFatalError ?? (() => throttledReloadDashboard())
27650
- });
27651
- }
28648
+ useHlsStreamWithCropping(videoRef, canvasRef, {
28649
+ src: hlsUrl,
28650
+ shouldPlay,
28651
+ cropping,
28652
+ canvasFps,
28653
+ useRAF,
28654
+ onFatalError: onFatalError ?? (() => throttledReloadDashboard())
28655
+ });
27652
28656
  const workspaceDisplayName = displayName || getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id);
27653
28657
  const efficiencyColor = getEfficiencyColor(workspace.efficiency, effectiveLegend);
27654
28658
  const efficiencyOverlayClass = efficiencyColor === "green" ? "bg-[#00D654]/25" : efficiencyColor === "yellow" ? "bg-[#FFD700]/30" : "bg-[#FF2D0A]/30";
@@ -27783,6 +28787,8 @@ var VideoGridView = React25__default.memo(({
27783
28787
  className = "",
27784
28788
  legend,
27785
28789
  videoSources = {},
28790
+ videoStreamsByWorkspaceId,
28791
+ videoStreamsLoading = false,
27786
28792
  displayNames = {},
27787
28793
  onWorkspaceHover,
27788
28794
  onWorkspaceHoverEnd
@@ -27794,6 +28800,15 @@ var VideoGridView = React25__default.memo(({
27794
28800
  const [gridRows, setGridRows] = useState(1);
27795
28801
  const [visibleWorkspaces, setVisibleWorkspaces] = useState(/* @__PURE__ */ new Set());
27796
28802
  const [failedStreams, setFailedStreams] = useState(/* @__PURE__ */ new Set());
28803
+ const [r2FallbackWorkspaces, setR2FallbackWorkspaces] = useState(/* @__PURE__ */ new Set());
28804
+ const workspaceIdsKey = useMemo(() => {
28805
+ const ids = workspaces.map((ws) => ws.workspace_uuid || ws.workspace_name);
28806
+ return ids.sort().join(",");
28807
+ }, [workspaces]);
28808
+ useEffect(() => {
28809
+ setFailedStreams(/* @__PURE__ */ new Set());
28810
+ setR2FallbackWorkspaces(/* @__PURE__ */ new Set());
28811
+ }, [workspaceIdsKey]);
27797
28812
  const videoConfig = useVideoConfig();
27798
28813
  const dashboardConfig = useDashboardConfig();
27799
28814
  const { cropping, canvasConfig, hlsUrls } = videoConfig;
@@ -27813,13 +28828,16 @@ var VideoGridView = React25__default.memo(({
27813
28828
  }
27814
28829
  return mergedVideoSources.workspaceHlsUrls[wsName] || mergedVideoSources.defaultHlsUrl;
27815
28830
  }, [mergedVideoSources]);
27816
- const getWorkspaceCropping = useCallback((workspaceName) => {
28831
+ const getWorkspaceCropping = useCallback((workspaceId, workspaceName) => {
28832
+ if (videoStreamsByWorkspaceId) {
28833
+ return videoStreamsByWorkspaceId[workspaceId]?.crop ?? void 0;
28834
+ }
27817
28835
  if (!cropping) return void 0;
27818
28836
  if (cropping.workspaceOverrides?.[workspaceName]) {
27819
28837
  return cropping.workspaceOverrides[workspaceName];
27820
28838
  }
27821
28839
  return cropping.default;
27822
- }, [cropping]);
28840
+ }, [cropping, videoStreamsByWorkspaceId]);
27823
28841
  const filteredWorkspaces = useMemo(() => {
27824
28842
  return selectedLine === 1 ? workspaces.filter((w) => {
27825
28843
  if (w.workspace_name === "WS5-5") return true;
@@ -27841,6 +28859,7 @@ var VideoGridView = React25__default.memo(({
27841
28859
  }
27842
28860
  }) : workspaces;
27843
28861
  }, [workspaces, selectedLine]);
28862
+ const streamsReady = !videoStreamsLoading;
27844
28863
  const calculateOptimalGrid = useCallback(() => {
27845
28864
  if (!containerRef.current) return;
27846
28865
  const containerPadding = 16;
@@ -27944,12 +28963,31 @@ var VideoGridView = React25__default.memo(({
27944
28963
  const navParams = getWorkspaceNavigationParams(workspaceId, displayName, workspace.line_id);
27945
28964
  router.push(`/workspace/${workspaceId}${navParams}`);
27946
28965
  }, [router, dashboardConfig]);
27947
- const handleStreamError = useCallback((workspaceId) => {
28966
+ const handleStreamError = useCallback((workspaceId, options) => {
28967
+ const isR2Stream = options?.isR2Stream ?? false;
28968
+ const hasFallback = Boolean(options?.fallbackUrl);
28969
+ if (isR2Stream && hasFallback) {
28970
+ console.warn(`[VideoGridView] R2 stream failed for workspace: ${workspaceId}. Falling back to media config.`);
28971
+ setR2FallbackWorkspaces((prev) => new Set(prev).add(workspaceId));
28972
+ setFailedStreams((prev) => {
28973
+ const next = new Set(prev);
28974
+ next.delete(workspaceId);
28975
+ return next;
28976
+ });
28977
+ trackCoreEvent("Video Stream Error", {
28978
+ workspace_id: workspaceId,
28979
+ view_type: "video_grid",
28980
+ stream_source: "r2",
28981
+ fallback: "media_config"
28982
+ });
28983
+ return;
28984
+ }
27948
28985
  console.error(`[VideoGridView] Stream failed for workspace: ${workspaceId}`);
27949
28986
  setFailedStreams((prev) => new Set(prev).add(workspaceId));
27950
28987
  trackCoreEvent("Video Stream Error", {
27951
28988
  workspace_id: workspaceId,
27952
- view_type: "video_grid"
28989
+ view_type: "video_grid",
28990
+ stream_source: isR2Stream ? "r2" : "media_config"
27953
28991
  });
27954
28992
  }, []);
27955
28993
  return /* @__PURE__ */ jsx("div", { className: `relative overflow-hidden h-full w-full ${className}`, children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "h-full w-full p-3 sm:p-2", children: /* @__PURE__ */ jsx(
@@ -27961,57 +28999,92 @@ var VideoGridView = React25__default.memo(({
27961
28999
  gridTemplateRows: `repeat(${gridRows}, 1fr)`,
27962
29000
  gridAutoFlow: "row"
27963
29001
  },
27964
- children: filteredWorkspaces.sort((a, b) => {
27965
- if (a.line_id !== b.line_id) {
27966
- return (a.line_id || "").localeCompare(b.line_id || "");
27967
- }
27968
- const aMatch = a.workspace_name.match(/WS(\d+)/);
27969
- const bMatch = b.workspace_name.match(/WS(\d+)/);
27970
- if (aMatch && bMatch) {
27971
- const aNum = parseInt(aMatch[1]);
27972
- const bNum = parseInt(bMatch[1]);
27973
- return aNum - bNum;
27974
- }
27975
- return a.workspace_name.localeCompare(b.workspace_name);
27976
- }).map((workspace) => {
27977
- const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
27978
- const workspaceKey = `${workspace.line_id || "unknown"}-${workspaceId}`;
27979
- const isVisible = visibleWorkspaces.has(workspaceId);
27980
- const isVeryLowEfficiency = workspace.show_exclamation ?? (workspace.efficiency < 50 && workspace.efficiency >= 10);
27981
- const workspaceCropping = getWorkspaceCropping(workspace.workspace_name);
27982
- return /* @__PURE__ */ jsx(
29002
+ children: (() => {
29003
+ const sortedWorkspaces = [...filteredWorkspaces].sort((a, b) => {
29004
+ if (a.line_id !== b.line_id) {
29005
+ return (a.line_id || "").localeCompare(b.line_id || "");
29006
+ }
29007
+ const aMatch = a.workspace_name.match(/WS(\d+)/);
29008
+ const bMatch = b.workspace_name.match(/WS(\d+)/);
29009
+ if (aMatch && bMatch) {
29010
+ const aNum = parseInt(aMatch[1]);
29011
+ const bNum = parseInt(bMatch[1]);
29012
+ return aNum - bNum;
29013
+ }
29014
+ return a.workspace_name.localeCompare(b.workspace_name);
29015
+ });
29016
+ const workspaceCards = sortedWorkspaces.map((workspace) => {
29017
+ const workspaceId = workspace.workspace_uuid || workspace.workspace_name;
29018
+ `${workspace.line_id || "unknown"}-${workspaceId}`;
29019
+ const isVisible = visibleWorkspaces.has(workspaceId);
29020
+ const isVeryLowEfficiency = workspace.show_exclamation ?? (workspace.efficiency < 50 && workspace.efficiency >= 10);
29021
+ const workspaceCropping = getWorkspaceCropping(workspaceId, workspace.workspace_name);
29022
+ const workspaceStream = videoStreamsByWorkspaceId?.[workspaceId];
29023
+ const r2Url = workspaceStream?.hls_url;
29024
+ const fallbackUrl = getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id);
29025
+ const hasR2Stream = Boolean(r2Url);
29026
+ const useFallback = r2FallbackWorkspaces.has(workspaceId) || streamsReady && !hasR2Stream;
29027
+ const hlsUrl = useFallback ? fallbackUrl : r2Url ?? "";
29028
+ const isR2Stream = !useFallback && hasR2Stream;
29029
+ const canAttemptR2 = hasR2Stream && !r2FallbackWorkspaces.has(workspaceId);
29030
+ const shouldPlay = isVisible && Boolean(hlsUrl) && (!failedStreams.has(workspaceId) || canAttemptR2);
29031
+ return {
29032
+ workspace,
29033
+ workspaceId,
29034
+ isVisible,
29035
+ isVeryLowEfficiency,
29036
+ workspaceCropping,
29037
+ fallbackUrl,
29038
+ hlsUrl,
29039
+ isR2Stream,
29040
+ shouldPlay
29041
+ };
29042
+ });
29043
+ const croppedActiveCount = workspaceCards.reduce((count, card) => {
29044
+ if (card.shouldPlay && card.workspaceCropping) {
29045
+ return count + 1;
29046
+ }
29047
+ return count;
29048
+ }, 0);
29049
+ const throttleCropping = croppedActiveCount > 10;
29050
+ const effectiveCanvasFps = throttleCropping ? 10 : canvasConfig?.fps;
29051
+ const effectiveUseRAF = throttleCropping ? false : canvasConfig?.useRAF;
29052
+ return workspaceCards.map((card) => /* @__PURE__ */ jsx(
27983
29053
  "div",
27984
29054
  {
27985
- "data-workspace-id": workspaceId,
29055
+ "data-workspace-id": card.workspaceId,
27986
29056
  className: "workspace-card relative w-full h-full",
27987
29057
  children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0", children: /* @__PURE__ */ jsx(
27988
29058
  VideoCard,
27989
29059
  {
27990
- workspace,
27991
- hlsUrl: getWorkspaceHlsUrl(workspace.workspace_name, workspace.line_id),
27992
- shouldPlay: isVisible && !failedStreams.has(workspaceId),
27993
- onClick: () => handleWorkspaceClick(workspace),
27994
- onFatalError: () => handleStreamError(workspaceId),
27995
- isVeryLowEfficiency,
29060
+ workspace: card.workspace,
29061
+ hlsUrl: card.hlsUrl,
29062
+ shouldPlay: card.shouldPlay,
29063
+ onClick: () => handleWorkspaceClick(card.workspace),
29064
+ onFatalError: () => handleStreamError(card.workspaceId, {
29065
+ isR2Stream: card.isR2Stream,
29066
+ fallbackUrl: card.fallbackUrl
29067
+ }),
29068
+ isVeryLowEfficiency: card.isVeryLowEfficiency,
27996
29069
  legend: effectiveLegend,
27997
- cropping: workspaceCropping,
27998
- canvasFps: canvasConfig?.fps,
29070
+ cropping: card.workspaceCropping,
29071
+ canvasFps: effectiveCanvasFps,
27999
29072
  displayName: (
28000
29073
  // Create line-aware lookup key: lineId_workspaceName
28001
29074
  // This ensures correct mapping when multiple lines have same workspace names
28002
- displayNames[`${workspace.line_id}_${workspace.workspace_name}`] || // Always pass line_id to fallback to ensure correct mapping per line
28003
- getWorkspaceDisplayName(workspace.workspace_name, workspace.line_id)
29075
+ displayNames[`${card.workspace.line_id}_${card.workspace.workspace_name}`] || // Always pass line_id to fallback to ensure correct mapping per line
29076
+ getWorkspaceDisplayName(card.workspace.workspace_name, card.workspace.line_id)
28004
29077
  ),
28005
- useRAF: canvasConfig?.useRAF,
29078
+ useRAF: effectiveUseRAF,
28006
29079
  compact: !selectedLine,
28007
- onMouseEnter: onWorkspaceHover ? () => onWorkspaceHover(workspaceId) : void 0,
28008
- onMouseLeave: onWorkspaceHoverEnd ? () => onWorkspaceHoverEnd(workspaceId) : void 0
29080
+ onMouseEnter: onWorkspaceHover ? () => onWorkspaceHover(card.workspaceId) : void 0,
29081
+ onMouseLeave: onWorkspaceHoverEnd ? () => onWorkspaceHoverEnd(card.workspaceId) : void 0
28009
29082
  }
28010
29083
  ) })
28011
29084
  },
28012
- workspaceKey
28013
- );
28014
- })
29085
+ card.workspaceId
29086
+ ));
29087
+ })()
28015
29088
  }
28016
29089
  ) }) });
28017
29090
  });
@@ -29647,33 +30720,6 @@ var VideoControls = ({
29647
30720
  }
29648
30721
  );
29649
30722
  };
29650
-
29651
- // src/lib/utils/r2Detection.ts
29652
- function isR2WorkerUrl(url, r2WorkerDomain) {
29653
- if (!url || !r2WorkerDomain) return false;
29654
- try {
29655
- const workerDomain = new URL(r2WorkerDomain).hostname;
29656
- return url.includes(workerDomain);
29657
- } catch {
29658
- return url.includes(r2WorkerDomain.replace(/https?:\/\//, ""));
29659
- }
29660
- }
29661
-
29662
- // src/lib/services/hlsAuthService.ts
29663
- async function getAuthTokenForHls(supabase) {
29664
- try {
29665
- const { data: { session } } = await supabase.auth.getSession();
29666
- if (!session?.access_token) {
29667
- console.warn("[HLS Auth] No active session, R2 streaming may fail");
29668
- return null;
29669
- }
29670
- console.log("[HLS Auth] Retrieved token for HLS.js requests");
29671
- return session.access_token;
29672
- } catch (error) {
29673
- console.error("[HLS Auth] Error getting auth token:", error);
29674
- return null;
29675
- }
29676
- }
29677
30723
  var ERROR_MAPPING = {
29678
30724
  "networkError": {
29679
30725
  code: 2,
@@ -30058,8 +31104,8 @@ var HlsVideoPlayer = forwardRef(({
30058
31104
  console.log(`[HlsVideoPlayer] Remuxing enabled - using remux playlist URL: ${remuxUrl}`);
30059
31105
  if (safariMode) {
30060
31106
  video.src = remuxUrl;
30061
- } else if (Hls3.isSupported()) {
30062
- const hls = new Hls3(mergedHlsConfig);
31107
+ } else if (Hls.isSupported()) {
31108
+ const hls = new Hls(mergedHlsConfig);
30063
31109
  hlsRef.current = hls;
30064
31110
  hls.on(Events.MANIFEST_PARSED, () => {
30065
31111
  console.log("[HlsVideoPlayer] Remuxed manifest parsed, ready to play");
@@ -30109,12 +31155,12 @@ var HlsVideoPlayer = forwardRef(({
30109
31155
  blobUrlRef.current = blobUrl;
30110
31156
  video.src = blobUrl;
30111
31157
  }
30112
- } else if (Hls3.isSupported()) {
31158
+ } else if (Hls.isSupported()) {
30113
31159
  const blob = new Blob([effectiveSrc], { type: "application/vnd.apple.mpegurl" });
30114
31160
  const blobUrl = URL.createObjectURL(blob);
30115
31161
  blobUrlRef.current = blobUrl;
30116
31162
  console.log(`[HlsVideoPlayer] Non-Safari browser (${browserName}) - using HLS.js with blob URL`);
30117
- const hls = new Hls3(mergedHlsConfig);
31163
+ const hls = new Hls(mergedHlsConfig);
30118
31164
  hlsRef.current = hls;
30119
31165
  hls.on(Events.MANIFEST_PARSED, () => {
30120
31166
  console.log("[HlsVideoPlayer] Manifest parsed, ready to play");
@@ -30164,8 +31210,8 @@ var HlsVideoPlayer = forwardRef(({
30164
31210
  onError?.(player, errorInfo);
30165
31211
  }
30166
31212
  } else {
30167
- if (Hls3.isSupported() && !isSafari()) {
30168
- const hls = new Hls3(mergedHlsConfig);
31213
+ if (Hls.isSupported() && !isSafari()) {
31214
+ const hls = new Hls(mergedHlsConfig);
30169
31215
  hlsRef.current = hls;
30170
31216
  hls.on(Events.MANIFEST_PARSED, () => {
30171
31217
  setIsReady(true);
@@ -31017,25 +32063,8 @@ var CroppedHlsVideoPlayer = forwardRef(({
31017
32063
  });
31018
32064
  CroppedHlsVideoPlayer.displayName = "CroppedHlsVideoPlayer";
31019
32065
  var CroppedVideoPlayer = CroppedHlsVideoPlayer;
31020
- var getSupabaseClient2 = () => {
31021
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
31022
- const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
31023
- if (!url || !key) {
31024
- throw new Error("Supabase configuration missing");
31025
- }
31026
- return createClient(url, key);
31027
- };
31028
- var getAuthToken4 = async () => {
31029
- try {
31030
- const supabase = getSupabaseClient2();
31031
- const { data: { session } } = await supabase.auth.getSession();
31032
- return session?.access_token || null;
31033
- } catch (error) {
31034
- console.error("[useWorkspaceCrop] Error getting auth token:", error);
31035
- return null;
31036
- }
31037
- };
31038
32066
  function useWorkspaceCrop(workspaceId) {
32067
+ const supabase = useSupabase();
31039
32068
  const [crop, setCrop] = useState(null);
31040
32069
  const [isLoading, setIsLoading] = useState(true);
31041
32070
  const [error, setError] = useState(null);
@@ -31048,7 +32077,8 @@ function useWorkspaceCrop(workspaceId) {
31048
32077
  setIsLoading(true);
31049
32078
  setError(null);
31050
32079
  try {
31051
- const token = await getAuthToken4();
32080
+ const { data: { session } } = await supabase.auth.getSession();
32081
+ const token = session?.access_token || null;
31052
32082
  if (!token) {
31053
32083
  throw new Error("Authentication required");
31054
32084
  }
@@ -31078,7 +32108,7 @@ function useWorkspaceCrop(workspaceId) {
31078
32108
  }
31079
32109
  };
31080
32110
  fetchCrop();
31081
- }, [workspaceId]);
32111
+ }, [workspaceId, supabase]);
31082
32112
  return { crop, isLoading, error };
31083
32113
  }
31084
32114
  function Skeleton({ className, ...props }) {
@@ -32067,6 +33097,7 @@ var FileManagerFilters = ({
32067
33097
  const [idleLabelFilter, setIdleLabelFilter] = useState(null);
32068
33098
  const [showIdleLabelFilterModal, setShowIdleLabelFilterModal] = useState(false);
32069
33099
  const timezone = useAppTimezone();
33100
+ const supabase = useSupabase();
32070
33101
  const [clipMetadata, setClipMetadata] = useState({});
32071
33102
  const [loadingCategories, setLoadingCategories] = useState(/* @__PURE__ */ new Set());
32072
33103
  const [categoryPages, setCategoryPages] = useState({});
@@ -32137,7 +33168,7 @@ var FileManagerFilters = ({
32137
33168
  method: "POST",
32138
33169
  headers: {
32139
33170
  "Content-Type": "application/json",
32140
- "Authorization": `Bearer ${await getAuthToken5()}`
33171
+ "Authorization": `Bearer ${await getAuthToken4()}`
32141
33172
  },
32142
33173
  body: JSON.stringify({
32143
33174
  action: "clip-metadata",
@@ -32170,13 +33201,8 @@ var FileManagerFilters = ({
32170
33201
  });
32171
33202
  }
32172
33203
  }, [workspaceId, date, shift]);
32173
- const getAuthToken5 = async () => {
33204
+ const getAuthToken4 = async () => {
32174
33205
  try {
32175
- const { createClient: createClient5 } = await import('@supabase/supabase-js');
32176
- const supabase = createClient5(
32177
- process.env.NEXT_PUBLIC_SUPABASE_URL || "",
32178
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ""
32179
- );
32180
33206
  const { data: { session } } = await supabase.auth.getSession();
32181
33207
  return session?.access_token || null;
32182
33208
  } catch (error) {
@@ -32196,7 +33222,7 @@ var FileManagerFilters = ({
32196
33222
  method: "POST",
32197
33223
  headers: {
32198
33224
  "Content-Type": "application/json",
32199
- "Authorization": `Bearer ${await getAuthToken5()}`
33225
+ "Authorization": `Bearer ${await getAuthToken4()}`
32200
33226
  },
32201
33227
  body: JSON.stringify({
32202
33228
  action: "percentile-clips",
@@ -33403,6 +34429,7 @@ var BottlenecksContent = ({
33403
34429
  }) => {
33404
34430
  console.log("\u{1F3AB} [BottlenecksContent] Rendered with ticketId:", ticketId || "NONE", "workspaceId:", workspaceId, "date:", date, "shift:", shift);
33405
34431
  const dashboardConfig = useDashboardConfig();
34432
+ const supabase = useSupabase();
33406
34433
  const { session } = useAuth();
33407
34434
  const timezone = useAppTimezone();
33408
34435
  const { shiftConfig, isLoading: isShiftConfigLoading } = useDynamicShiftConfig(lineId);
@@ -33768,26 +34795,21 @@ var BottlenecksContent = ({
33768
34795
  fetchClipCounts();
33769
34796
  }
33770
34797
  }, [workspaceId, effectiveDateString, effectiveShiftId, s3ClipsService, fetchClipCounts, isEffectiveShiftReady]);
33771
- const getAuthToken5 = useCallback(async () => {
34798
+ const getAuthToken4 = useCallback(async () => {
33772
34799
  try {
33773
- const { createClient: createClient5 } = await import('@supabase/supabase-js');
33774
- const supabase = createClient5(
33775
- process.env.NEXT_PUBLIC_SUPABASE_URL || "",
33776
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ""
33777
- );
33778
34800
  const { data: { session: session2 } } = await supabase.auth.getSession();
33779
34801
  return session2?.access_token || null;
33780
34802
  } catch (error2) {
33781
34803
  console.error("[BottlenecksContent] Error getting auth token:", error2);
33782
34804
  return null;
33783
34805
  }
33784
- }, []);
34806
+ }, [supabase]);
33785
34807
  useEffect(() => {
33786
34808
  if (!triageMode || !workspaceId || !isEffectiveShiftReady) return;
33787
34809
  const fetchTriageClips = async () => {
33788
34810
  setIsLoadingTriageClips(true);
33789
34811
  try {
33790
- const token = await getAuthToken5();
34812
+ const token = await getAuthToken4();
33791
34813
  if (!token) {
33792
34814
  console.error("[BottlenecksContent] No auth token available");
33793
34815
  return;
@@ -33835,7 +34857,7 @@ var BottlenecksContent = ({
33835
34857
  }
33836
34858
  };
33837
34859
  fetchTriageClips();
33838
- }, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken5, isEffectiveShiftReady]);
34860
+ }, [triageMode, workspaceId, effectiveDateString, effectiveShiftId, getAuthToken4, isEffectiveShiftReady]);
33839
34861
  useEffect(() => {
33840
34862
  if (!triageMode || triageClips.length === 0 || !session?.access_token) {
33841
34863
  return;
@@ -34028,13 +35050,7 @@ var BottlenecksContent = ({
34028
35050
  return;
34029
35051
  }
34030
35052
  console.log(`[BottlenecksContent] Loading metadata for category: ${categoryId}${forceRefresh ? " (force refresh)" : ""}`);
34031
- const { createClient: createClient5 } = await import('@supabase/supabase-js');
34032
- const supabase = createClient5(
34033
- process.env.NEXT_PUBLIC_SUPABASE_URL || "",
34034
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ""
34035
- );
34036
- const { data: { session: session2 } } = await supabase.auth.getSession();
34037
- const authToken = session2?.access_token;
35053
+ const authToken = await getAuthToken4();
34038
35054
  if (!authToken) {
34039
35055
  console.error("Authentication required for metadata loading");
34040
35056
  return;
@@ -34104,10 +35120,10 @@ var BottlenecksContent = ({
34104
35120
  setCategoryMetadata(metadataClips);
34105
35121
  categoryMetadataRef.current = metadataClips;
34106
35122
  console.log(`[BottlenecksContent] Loaded metadata for ${categoryId}: ${metadataClips.length} clips`);
34107
- if (categoryId === "idle_time" && metadataClips.length > 0 && session2?.access_token) {
35123
+ if (categoryId === "idle_time" && metadataClips.length > 0 && session?.access_token) {
34108
35124
  const idleClipIds2 = metadataClips.map((c) => c.clipId).filter(Boolean);
34109
35125
  console.log(`[BottlenecksContent] Fetching classifications for ${idleClipIds2.length} metadata clips`);
34110
- fetchClassifications(idleClipIds2, session2.access_token).then((classifications) => {
35126
+ fetchClassifications(idleClipIds2, session.access_token).then((classifications) => {
34111
35127
  console.log("[BottlenecksContent] Received metadata classifications:", Object.keys(classifications).length);
34112
35128
  setClipClassifications((prev) => ({
34113
35129
  ...prev,
@@ -40523,6 +41539,8 @@ var WorkspaceGrid = React25__default.memo(({
40523
41539
  hasFlowBuffers = false,
40524
41540
  legend = DEFAULT_EFFICIENCY_LEGEND,
40525
41541
  videoSources = {},
41542
+ videoStreamsByWorkspaceId = {},
41543
+ videoStreamsLoading = false,
40526
41544
  displayNames = {},
40527
41545
  onWorkspaceHover,
40528
41546
  onWorkspaceHoverEnd
@@ -40590,6 +41608,8 @@ var WorkspaceGrid = React25__default.memo(({
40590
41608
  {
40591
41609
  workspaces,
40592
41610
  videoSources,
41611
+ videoStreamsByWorkspaceId,
41612
+ videoStreamsLoading,
40593
41613
  displayNames,
40594
41614
  legend,
40595
41615
  onWorkspaceHover,
@@ -43541,26 +44561,26 @@ var SingleVideoStream = ({
43541
44561
  const hlsStreamUrl = streamUrl || getCameraStreamUrl(workspaceName, baseUrl);
43542
44562
  console.log(`Using camera URL for ${workspaceName}: ${hlsStreamUrl}`);
43543
44563
  const mergedHlsConfig = { ...DEFAULT_HLS_CONFIG, ...hlsConfig };
43544
- if (Hls3.isSupported()) {
43545
- const hls = new Hls3(mergedHlsConfig);
44564
+ if (Hls.isSupported()) {
44565
+ const hls = new Hls(mergedHlsConfig);
43546
44566
  hlsRef.current = hls;
43547
- hls.on(Hls3.Events.MEDIA_ATTACHED, () => {
44567
+ hls.on(Hls.Events.MEDIA_ATTACHED, () => {
43548
44568
  console.log("HLS media attached");
43549
44569
  hls.loadSource(hlsStreamUrl);
43550
44570
  });
43551
- hls.on(Hls3.Events.MANIFEST_PARSED, () => {
44571
+ hls.on(Hls.Events.MANIFEST_PARSED, () => {
43552
44572
  console.log("HLS manifest parsed");
43553
44573
  attemptPlay(video);
43554
44574
  });
43555
- hls.on(Hls3.Events.ERROR, (_, data) => {
44575
+ hls.on(Hls.Events.ERROR, (_, data) => {
43556
44576
  if (data.fatal) {
43557
44577
  console.error("Fatal HLS error:", data.type, data.details);
43558
44578
  switch (data.type) {
43559
- case Hls3.ErrorTypes.NETWORK_ERROR:
44579
+ case Hls.ErrorTypes.NETWORK_ERROR:
43560
44580
  console.error("Fatal network error encountered");
43561
44581
  setError("Network error: Please check your connection");
43562
44582
  break;
43563
- case Hls3.ErrorTypes.MEDIA_ERROR:
44583
+ case Hls.ErrorTypes.MEDIA_ERROR:
43564
44584
  console.error("Fatal media error encountered, trying to recover");
43565
44585
  hls.recoverMediaError();
43566
44586
  break;
@@ -48527,6 +49547,10 @@ function HomeView({
48527
49547
  JSON.stringify(workspaceMetrics.map((w) => `${w.workspace_uuid}-${Math.round(w.efficiency)}-${w.trend}`)),
48528
49548
  selectedLineId
48529
49549
  ]);
49550
+ const {
49551
+ streamsByWorkspaceId: videoStreamsByWorkspaceId,
49552
+ isLoading: videoStreamsLoading
49553
+ } = useWorkspaceVideoStreams(workspaceMetrics);
48530
49554
  const memoizedKPIs = useMemo(() => kpis, [
48531
49555
  // Only update reference when values change by at least 1%
48532
49556
  kpis?.efficiency?.value ? Math.round(kpis.efficiency.value) : null,
@@ -48721,6 +49745,8 @@ function HomeView({
48721
49745
  factoryView: factoryViewId,
48722
49746
  legend: efficiencyLegend,
48723
49747
  videoSources,
49748
+ videoStreamsByWorkspaceId,
49749
+ videoStreamsLoading,
48724
49750
  displayNames: workspaceDisplayNames,
48725
49751
  hasFlowBuffers,
48726
49752
  className: "h-full",
@@ -48751,6 +49777,8 @@ function HomeView({
48751
49777
  factoryView: factoryViewId,
48752
49778
  legend: efficiencyLegend,
48753
49779
  videoSources,
49780
+ videoStreamsByWorkspaceId,
49781
+ videoStreamsLoading,
48754
49782
  displayNames: workspaceDisplayNames,
48755
49783
  hasFlowBuffers,
48756
49784
  className: "h-full",
@@ -61565,7 +62593,11 @@ async function getManufacturingInsights(userQuestion, lineId, shiftId, companyId
61565
62593
  );
61566
62594
  }
61567
62595
  function createStreamProxyHandler(config) {
61568
- const cloudFrontDomain = config?.cloudFrontDomain || "https://d1eiob0chi5jw.cloudfront.net";
62596
+ const baseUrl = config?.baseUrl || config?.cloudFrontDomain || "https://d1eiob0chi5jw.cloudfront.net";
62597
+ const forwardAuth = config?.forwardAuth ?? false;
62598
+ const noCacheManifest = config?.noCacheManifest ?? false;
62599
+ const staticAuthToken = config?.staticAuthToken;
62600
+ const debugAuth = config?.debugAuth ?? false;
61569
62601
  return async function handler(req, res) {
61570
62602
  if (req.method !== "GET") {
61571
62603
  return res.status(405).json({ error: "Method not allowed" });
@@ -61575,15 +62607,28 @@ function createStreamProxyHandler(config) {
61575
62607
  return res.status(400).json({ error: "Missing path parameter" });
61576
62608
  }
61577
62609
  const fullPath = path.join("/");
61578
- const targetUrl = `${cloudFrontDomain}/${fullPath}`;
62610
+ const targetUrl = `${baseUrl}/${fullPath}`;
61579
62611
  try {
61580
62612
  console.log(`[HLS Stream Proxy] Streaming: ${targetUrl}`);
62613
+ const resolvedAuthHeader = (() => {
62614
+ if (forwardAuth && req.headers.authorization) {
62615
+ return req.headers.authorization;
62616
+ }
62617
+ if (staticAuthToken) {
62618
+ return staticAuthToken.startsWith("Bearer ") ? staticAuthToken : `Bearer ${staticAuthToken}`;
62619
+ }
62620
+ return void 0;
62621
+ })();
62622
+ if (debugAuth) {
62623
+ console.log("[HLS Stream Proxy] Auth header present:", Boolean(resolvedAuthHeader));
62624
+ }
61581
62625
  const response = await fetch(targetUrl, {
61582
62626
  method: "GET",
61583
62627
  headers: {
61584
62628
  "User-Agent": "NextJS-HLS-Stream-Proxy",
61585
62629
  // Pass through range headers for partial content requests
61586
- ...req.headers.range && { "Range": req.headers.range }
62630
+ ...req.headers.range && { "Range": req.headers.range },
62631
+ ...resolvedAuthHeader ? { "Authorization": resolvedAuthHeader } : {}
61587
62632
  }
61588
62633
  });
61589
62634
  if (!response.ok) {
@@ -61604,6 +62649,10 @@ function createStreamProxyHandler(config) {
61604
62649
  }
61605
62650
  if (fullPath.endsWith(".m3u8")) {
61606
62651
  res.setHeader("Content-Type", "application/vnd.apple.mpegurl");
62652
+ if (noCacheManifest) {
62653
+ res.setHeader("Cache-Control", "no-store, must-revalidate");
62654
+ res.setHeader("Pragma", "no-cache");
62655
+ }
61607
62656
  } else if (fullPath.endsWith(".ts")) {
61608
62657
  res.setHeader("Content-Type", "video/mp2t");
61609
62658
  }
@@ -61657,4 +62706,4 @@ var streamProxyConfig = {
61657
62706
  }
61658
62707
  };
61659
62708
 
61660
- export { ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, TeamUsagePdfGenerator, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, aggregateKPIsFromLineMetricsRows, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, buildDateKey, buildKPIsFromLineMetricsRow, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, fetchIdleTimeReasons, filterDataByDateKeyRange, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAvailableShiftIds, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getNextUpdateInterval, getOperationalDate, getReasonColor, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isFullMonthRange, isLegacyConfiguration, isPrefetchError, isSafari, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeDateKeyRange, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useCompanyUsersUsage, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMultiLineShiftConfigs, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, userService, videoPrefetchManager, videoPreloader, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };
62709
+ export { ACTION_NAMES, AIAgentView_default as AIAgentView, AcceptInvite, AcceptInviteView_default as AcceptInviteView, AdvancedFilterDialog, AdvancedFilterPanel, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthService, AuthenticatedBottleneckClipsView, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedTicketsView, AuthenticatedWorkspaceHealthView, AxelNotificationPopup, AxelOrb, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottleneckClipsModal, BottleneckClipsView_default as BottleneckClipsView, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, ChangeRoleDialog, ClipFilterProvider, CompactWorkspaceHealthCard, ConfirmRemoveUserDialog, CongratulationsOverlay, CroppedHlsVideoPlayer, CroppedVideoPlayer, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_HOME_VIEW_CONFIG, DEFAULT_MAP_VIEW_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_SHIFT_DATA, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, DiagnosisVideoModal, EmptyStateMessage, EncouragementOverlay, FactoryAssignmentDropdown, FactoryView_default as FactoryView, FileManagerFilters, FilterDialogTrigger, FirstTimeLoginDebug, FirstTimeLoginHandler, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthDateShiftSelector, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HlsVideoPlayer, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, ImprovementCenterView_default as ImprovementCenterView, InlineEditableText, InteractiveOnboardingTour, InvitationService, InvitationsTable, InviteUserDialog, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineAssignmentDropdown, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LinesService, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, Logo, MainLayout, MapGridView, MetricCard_default as MetricCard, MinimalOnboardingPopup, MobileMenuProvider, NewClipsNotification, NoWorkspaceData, OnboardingDemo, OnboardingTour, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PlayPauseIndicator, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, RoleBadge, S3ClipsSupabaseService as S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SessionTracker, SessionTrackingContext, SessionTrackingProvider, SettingsPopup, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SignupWithInvitation, SilentErrorBoundary, SimpleOnboardingPopup, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, SupervisorDropdown_default as SupervisorDropdown, SupervisorManagementView_default as SupervisorManagementView, SupervisorService, TargetWorkspaceGrid, TargetsView_default as TargetsView, TeamManagementView_default as TeamManagementView, TeamUsagePdfGenerator, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TicketsView_default as TicketsView, TimeDisplay_default as TimeDisplay, TimePickerDropdown, Timer_default as Timer, TimezoneProvider, TimezoneService, UserManagementService, UserManagementTable, UserService, UserUsageDetailModal, UserUsageStats, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMetricCardsImpl, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyHistory, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, aggregateKPIsFromLineMetricsRows, apiUtils, areAllLinesOnSameShift, authCoreService, authOTPService, authRateLimitService, buildDateKey, buildKPIsFromLineMetricsRow, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createDefaultKPIs, createInvitationService, createLinesService, createSessionTracker, createStreamProxyHandler, createSupabaseClient, createSupervisorService, createThrottledReload, createUserManagementService, createUserService, dashboardService, deleteThread, fetchIdleTimeReasons, filterDataByDateKeyRange, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateKeyForDisplay, formatDateTimeInZone, formatDuration, formatISTDate, formatIdleTime, formatRangeLabel, formatReasonLabel, formatRelativeTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAllWorkspaceDisplayNamesSnapshot, getAnonClient, getAvailableShiftIds, getBrowserName, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentShiftForLine, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDateKeyFromDate, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getMonthKeyBounds, getMonthWeekRanges, getNextUpdateInterval, getOperationalDate, getReasonColor, getS3SignedUrl, getS3VideoSrc, getShiftData, getShiftNameById, getShiftWorkDurationSeconds, getShortShiftName, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUniformShiftGroup, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, groupLinesByShift, hasAnyShiftData, identifyCoreUser, initializeCoreMixpanel, isFullMonthRange, isLegacyConfiguration, isPrefetchError, isSafari, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, linesService, mergeWithDefaultConfig, migrateLegacyConfiguration, normalizeDateKeyRange, optifyeAgentClient, parseDateKeyToDate, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, simulateApiDelay, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, subscribeWorkspaceDisplayNames, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, transformToChartData, updateThreadTitle, upsertWorkspaceDisplayNameInCache, useAccessControl, useActiveBreaks, useActiveLineId, useAllWorkspaceMetrics, useAnalyticsConfig, useAppTimezone, useAudioService, useAuth, useAuthConfig, useAxelNotifications, useCanSaveTargets, useClipFilter, useClipTypes, useClipTypesWithCounts, useCompanyUsersUsage, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useDynamicShiftConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHasLineAccess, useHideMobileHeader, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useIdleTimeClipClassifications, useIdleTimeReasons, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineShiftConfig, useLineSupervisor, useLineWorkspaceMetrics, useLines, useMessages, useMetrics, useMobileMenu, useMultiLineShiftConfigs, useNavigation, useOptionalSupabase, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useSessionKeepAlive, useSessionTracking, useSessionTrackingContext, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useSupervisorsByLineIds, useTargets, useTeamManagementPermissions, useTheme, useThemeConfig, useThreads, useTicketHistory, useTimezoneContext, useUserLineAccess, useUserUsage, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealthById, useWorkspaceHealthStatus, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, useWorkspaceUptimeTimeline, useWorkspaceVideoStreams, userService, videoPrefetchManager, videoPreloader, whatsappService, withAccessControl, withAuth, withRegistry, withTimezone, workspaceHealthService, workspaceService };