@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.d.mts +87 -4
- package/dist/index.d.ts +87 -4
- package/dist/index.js +1388 -337
- package/dist/index.mjs +1370 -321
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
7278
|
+
var useOptionalSupabase = () => {
|
|
7112
7279
|
const context = useContext(SupabaseContext);
|
|
7113
|
-
|
|
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
|
|
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
|
|
10875
|
-
maxBufferLength:
|
|
10876
|
-
//
|
|
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.
|
|
10882
|
-
//
|
|
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
|
|
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:
|
|
10903
|
-
levelLoadingTimeOut:
|
|
10904
|
-
fragLoadingTimeOut:
|
|
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:
|
|
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
|
-
//
|
|
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
|
-
|
|
11004
|
-
|
|
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
|
-
|
|
11015
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
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
|
-
},
|
|
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 (
|
|
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 (
|
|
11115
|
-
|
|
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
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
|
|
11126
|
-
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
|
|
11130
|
-
|
|
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
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
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
|
-
|
|
11149
|
-
|
|
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
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
11158
|
-
|
|
11159
|
-
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
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
|
|
17480
|
-
|
|
17481
|
-
|
|
17482
|
-
|
|
17483
|
-
|
|
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
|
-
|
|
17515
|
-
|
|
17516
|
-
|
|
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
|
|
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
|
-
|
|
27637
|
-
|
|
27638
|
-
|
|
27639
|
-
|
|
27640
|
-
|
|
27641
|
-
|
|
27642
|
-
|
|
27643
|
-
|
|
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:
|
|
27965
|
-
|
|
27966
|
-
|
|
27967
|
-
|
|
27968
|
-
|
|
27969
|
-
|
|
27970
|
-
|
|
27971
|
-
|
|
27972
|
-
|
|
27973
|
-
|
|
27974
|
-
|
|
27975
|
-
|
|
27976
|
-
|
|
27977
|
-
|
|
27978
|
-
const
|
|
27979
|
-
|
|
27980
|
-
|
|
27981
|
-
|
|
27982
|
-
|
|
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:
|
|
27992
|
-
shouldPlay:
|
|
27993
|
-
onClick: () => handleWorkspaceClick(workspace),
|
|
27994
|
-
onFatalError: () => handleStreamError(workspaceId
|
|
27995
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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 (
|
|
30062
|
-
const hls = new
|
|
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 (
|
|
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
|
|
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 (
|
|
30168
|
-
const hls = new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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 &&
|
|
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,
|
|
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 (
|
|
43545
|
-
const hls = new
|
|
44564
|
+
if (Hls.isSupported()) {
|
|
44565
|
+
const hls = new Hls(mergedHlsConfig);
|
|
43546
44566
|
hlsRef.current = hls;
|
|
43547
|
-
hls.on(
|
|
44567
|
+
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
|
43548
44568
|
console.log("HLS media attached");
|
|
43549
44569
|
hls.loadSource(hlsStreamUrl);
|
|
43550
44570
|
});
|
|
43551
|
-
hls.on(
|
|
44571
|
+
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
43552
44572
|
console.log("HLS manifest parsed");
|
|
43553
44573
|
attemptPlay(video);
|
|
43554
44574
|
});
|
|
43555
|
-
hls.on(
|
|
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
|
|
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
|
|
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
|
|
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 = `${
|
|
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 };
|