diff --git a/src/utils/timerange.js b/src/utils/timerange.js
index 48dfd07..9e374c1 100644
--- a/src/utils/timerange.js
+++ b/src/utils/timerange.js
@@ -226,3 +226,31 @@ export const parseTimerangeDateTime = (timerange) => {
};
}
};
+
+/**
+ * Whether a timerange covers any media at all.
+ *
+ * A growing flow reports an open-ended range (`[start_`), so it has segments
+ * but no computable duration -- duration is therefore not a usable proxy for
+ * "has media". The bounds are read from the string rather than from
+ * parseTimerange, which collapses "empty", "inverted" and "did not parse" onto
+ * the same 0n..0n result.
+ */
+export const timerangeHasMedia = (timerange) => {
+ if (typeof timerange !== "string" || !timerange) return false;
+ if (EMPTY_PATTERNS.has(timerange)) return false;
+
+ const match = TIMERANGE_REGEX.exec(timerange);
+ if (!match) return false;
+
+ const { startSeconds, startNanos, endSeconds, endNanos } = match.groups;
+ const hasStart = startSeconds != null || startNanos != null;
+ const hasEnd = endSeconds != null || endNanos != null;
+ if (!hasStart && !hasEnd) return false;
+
+ // A bare timestamp with no range separator is a single point, so zero-length.
+ if (!timerange.includes("_")) return false;
+ if (!hasStart || !hasEnd) return true;
+
+ return !(startSeconds === endSeconds && startNanos === endNanos);
+};
diff --git a/src/views/Embed/index.jsx b/src/views/Embed/index.jsx
index 6d95fc6..8aa0284 100644
--- a/src/views/Embed/index.jsx
+++ b/src/views/Embed/index.jsx
@@ -5,7 +5,7 @@ import { useIngestingFlows } from "@/hooks/useFlows";
import { useCapabilities } from "@/hooks/useService";
import useStoreManager from "@/stores/useStoreManager";
import StoreManager from "@/views/StoreManager";
-import { parseTimerange } from "@/utils/timerange";
+import { parseTimerange, timerangeHasMedia } from "@/utils/timerange";
import { isFlowGrowing } from "@/utils/flowStatus";
import { findVideoMember } from "@/utils/editorialPurpose";
import { TAMS_PAGE_LIMIT, VIEW_MODE } from "@/constants";
@@ -113,8 +113,8 @@ const resolveVideoSourceId = async (source, api, canQueryCollections) => {
};
/**
- * For each displayed source, fetch its video flow to get fps, duration and
- * growing status. Growing status comes from the flow's `status` (8.2) or the
+ * For each displayed source, fetch its video flow to get fps, duration,
+ * growing status, and whether any segments have landed yet. Growing status comes from the flow's `status` (8.2) or the
* deprecated flow_status tag, guarded by recent segment activity.
*
* On initial load, all sources are fetched once. Sources that are growing are
@@ -166,6 +166,7 @@ const useSourceFlowDetails = (displayedSources, liveSourceIds) => {
let durationMs = null;
const detailRes = await api.get(`/flows/${flow.id}?include_timerange=true`);
const tr = detailRes.data?.timerange;
+ const hasSegments = timerangeHasMedia(tr);
if (tr) {
const parsed = parseTimerange(tr);
if (parsed.start !== null && parsed.end !== null) {
@@ -183,7 +184,7 @@ const useSourceFlowDetails = (displayedSources, liveSourceIds) => {
if (!cancelled) {
setFlowDetails((prev) => {
const next = new Map(prev);
- next.set(source.id, { fps, isGrowing, durationMs });
+ next.set(source.id, { fps, isGrowing, durationMs, hasSegments });
return next;
});
}
@@ -216,6 +217,7 @@ const useSourceFlowDetails = (displayedSources, liveSourceIds) => {
let durationMs = null;
const tr = flow.timerange;
+ const hasSegments = timerangeHasMedia(tr);
if (tr) {
const parsed = parseTimerange(tr);
if (parsed.start !== null && parsed.end !== null) {
@@ -231,7 +233,7 @@ const useSourceFlowDetails = (displayedSources, liveSourceIds) => {
setFlowDetails((prev) => {
const next = new Map(prev);
const existing = next.get(sourceId);
- next.set(sourceId, { ...existing, isGrowing, durationMs });
+ next.set(sourceId, { ...existing, isGrowing, durationMs, hasSegments });
return next;
});
} catch { /* skip */ }
@@ -315,6 +317,7 @@ const Embed = () => {
...source,
isGrowing: details?.isGrowing ?? false,
durationMs: details?.durationMs ?? null,
+ hasSegments: details?.hasSegments ?? false,
fps: details?.fps ?? null,
detailsLoaded: !!details,
};
@@ -426,7 +429,7 @@ const Embed = () => {
) : source.durationMs > 0 ? (
{formatDuration(source.durationMs)}
) : null}
- {source.durationMs > 0 && (
+ {source.hasSegments && (