Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/actions/videos/new-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export async function newComment(data: {
type: type,
content: content,
videoId: videoId,
timestamp: timestamp || null,
timestamp: timestamp ?? null,
parentCommentId: parentCommentId,
createdAt: new Date(),
updatedAt: new Date(),
Expand Down
72 changes: 28 additions & 44 deletions apps/web/app/s/[videoId]/Share.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,55 +214,39 @@ export const Share = ({
const aiLoading = shouldShowLoading();

const handleSeek = useCallback((time: number) => {
let video = playerRef.current;

// Fallback to DOM query if ref is not available
if (!video) {
video = document.querySelector("video") as HTMLVideoElement;
if (!video) {
console.warn("Video player not ready");
return;
}
const v =
playerRef.current ??
(document.querySelector("video") as HTMLVideoElement | null);
if (!v) {
console.warn("Video player not ready");
return;
}

// Try to seek immediately if video has metadata
if (video.readyState >= 1) {
// HAVE_METADATA
const seekOnce = (t: number) => {
const dur =
Number.isFinite(v.duration) && v.duration > 0 ? v.duration : null;
const clamped = dur ? Math.max(0, Math.min(dur - 0.001, t)) : t;
try {
video.currentTime = time;
return;
} catch (error) {
console.error("Failed to seek video:", error);
v.currentTime = clamped;
} catch (e) {
console.error("Failed to seek video:", e);
}
}

// If video isn't ready, wait for it to be ready
const handleCanPlay = () => {
try {
video.currentTime = time;
} catch (error) {
console.error("Failed to seek video after canplay:", error);
}
video.removeEventListener("canplay", handleCanPlay);
};

const handleLoadedMetadata = () => {
try {
video.currentTime = time;
} catch (error) {
console.error("Failed to seek video after loadedmetadata:", error);
}
video.removeEventListener("loadedmetadata", handleLoadedMetadata);
if (v.readyState >= 1) {
seekOnce(time);
return;
}
let timeoutId: ReturnType<typeof setTimeout> | null = null;
const handleReady = () => {
seekOnce(time);
v.removeEventListener("canplay", handleReady);
v.removeEventListener("loadedmetadata", handleReady);
if (timeoutId) clearTimeout(timeoutId);
};

// Listen for multiple events to ensure we catch when the video is ready
video.addEventListener("canplay", handleCanPlay);
video.addEventListener("loadedmetadata", handleLoadedMetadata);

// Cleanup after 3 seconds if events don't fire
setTimeout(() => {
video.removeEventListener("canplay", handleCanPlay);
video.removeEventListener("loadedmetadata", handleLoadedMetadata);
v.addEventListener("canplay", handleReady, { once: true });
v.addEventListener("loadedmetadata", handleReady, { once: true });
timeoutId = setTimeout(() => {
v.removeEventListener("canplay", handleReady);
v.removeEventListener("loadedmetadata", handleReady);
}, 3000);
Comment on lines +247 to 250
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not drop readiness listeners after 3 s

If the video metadata takes longer than 3 seconds to arrive (slow network, large asset, cold CDN), this timeout removes both readiness listeners before they ever fire. Once that happens there is no remaining path to call seekOnce, so the user’s chapter/time jump silently fails. That’s a regression from the previous behavior that waited indefinitely for readiness. Please keep the listeners until they run, or at least only remove them after we actually succeed in seeking.

-    timeoutId = setTimeout(() => {
-      v.removeEventListener("canplay", handleReady);
-      v.removeEventListener("loadedmetadata", handleReady);
-    }, 3000);
+    timeoutId = setTimeout(() => {
+      if (v.readyState >= 1) {
+        seekOnce(time);
+        v.removeEventListener("canplay", handleReady);
+        v.removeEventListener("loadedmetadata", handleReady);
+        timeoutId = null;
+      }
+    }, 3000);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
timeoutId = setTimeout(() => {
v.removeEventListener("canplay", handleReady);
v.removeEventListener("loadedmetadata", handleReady);
}, 3000);
timeoutId = setTimeout(() => {
if (v.readyState >= 1) {
seekOnce(time);
v.removeEventListener("canplay", handleReady);
v.removeEventListener("loadedmetadata", handleReady);
timeoutId = null;
}
}, 3000);
🤖 Prompt for AI Agents
In apps/web/app/s/[videoId]/Share.tsx around lines 247 to 250, the code
currently drops the "canplay" and "loadedmetadata" readiness listeners after a
3s timeout which can prevent seekOnce from ever being called on slow networks;
remove that timeout logic and instead keep the listeners attached until they
fire, or alternatively only remove each listener after its handler successfully
runs (and only clear any timeout if/when the seek succeeds), ensuring there
remains a path to call seekOnce even when metadata arrives later than 3s.

}, []);

Expand Down
1 change: 0 additions & 1 deletion apps/web/app/s/[videoId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,6 @@ async function AuthorizedContent({
});
} catch (error) {
console.warn("Failed to create view notification:", error);
// Don't throw the error, just log it since this is not critical for the page to function
}
}

Expand Down
4 changes: 0 additions & 4 deletions apps/web/lib/Notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export async function createNotification(
notification: CreateNotificationInput,
) {
try {
// First, check if the video exists
const [videoExists] = await db()
.select({ id: videos.id, ownerId: videos.ownerId })
.from(videos)
Expand All @@ -44,7 +43,6 @@ export async function createNotification(
throw new Error(`Video not found for videoId: ${notification.videoId}`);
}

// Then get the owner data
const [ownerResult] = await db()
.select({
id: users.id,
Expand All @@ -63,8 +61,6 @@ export async function createNotification(
videoExists.ownerId,
"- skipping notification creation",
);
// Don't throw an error, just skip notification creation
// This handles cases where the video exists but the owner was deleted
return;
}

Expand Down
Loading