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
126 changes: 108 additions & 18 deletions app/dashboard/courses/[courseId]/CourseDetailPageClient.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
import VidPlayerBox from "@/components/atoms/dashboard/vid-player-box";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import Link from "next/link";
import React, { useState, useMemo } from "react";
import React, { useState, useMemo, useCallback } from "react";
import Button from "@/components/atoms/form/Button";
import StarRate from "@/components/atoms/form/StarRate";
import ReviewsSection from "@/components/organisms/dashboard/ReviewsSection";
import { useAuth } from "@/hooks/useAuth";
import { Textarea } from "@/components/ui/textarea";
import { addCourseReview } from "@/lib/actions/courses/addReview";
import { useHasCourse, usePurchaseCourse } from "@/hooks/usePurchase";
import { useCourseProgress, formatTime } from "@/hooks/useCourseProgress";
import { Progress } from "@/components/ui/progress";
import { toast } from "sonner";
import { Wallet } from "lucide-react";
import { Wallet, RotateCcw, Play } from "lucide-react";
import PaymentModal from "@/components/stellar/PaymentModal";
import { useStellar } from "@/components/stellar/StellarProvider";

Expand All @@ -26,23 +28,54 @@ export default function CourseDetailClient({ course }) {
const [loading, setLoading] = useState(false);
const [showPaymentModal, setShowPaymentModal] = useState(false);

// Check if user already owns the course
const hasCourse = useHasCourse(course?._id);
// Local state to hide button after purchase
const [purchased, setPurchased] = useState(false);

// Check if the current user has already reviewed
const {
progress,
reportProgress,
resetProgress,
resumeTime,
resumeLabel,
} = useCourseProgress(course?._id);

const [useResume, setUseResume] = useState(false);
const [playerKey, setPlayerKey] = useState(0);

const effectiveStartTime = useResume && resumeTime ? resumeTime : 0;
Comment on lines +34 to +45

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Surface the hook's loading state.

useCourseProgress returns loading, but it isn't destructured here. Until the GET /api/progress/courses round-trip resolves, progress.percent is 0, so the page renders as "no progress" and then the resume banner + progress bar pop in and shift the player down. Gating those blocks on !loading (or rendering a small skeleton) avoids the layout jump.

🛠️ Suggested wiring
   const {
     progress,
+    loading: progressLoading,
     reportProgress,
     resetProgress,
     resumeTime,
     resumeLabel,
   } = useCourseProgress(course?._id);

Then guard the resume banner and the elapsed-time block with !progressLoading.

As per path instructions, this is a Next.js 15 App Router file and I should flag "missing loading/error states for data fetching".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/dashboard/courses/`[courseId]/CourseDetailPageClient.jsx around lines 34
- 45, Destructure the loading state from useCourseProgress as progressLoading,
then gate the resume banner and elapsed-time/progress display blocks on
!progressLoading so they remain hidden or show their loading placeholder until
progress data resolves. Keep the existing progress and resume behavior unchanged
after loading completes.

Source: Path instructions


const handleTimeUpdate = useCallback(
(currentTime, duration) => {
reportProgress(currentTime, duration);
},
[reportProgress]
);

const handleEnded = useCallback(() => {
if (course?._id) {
reportProgress(progress.durationSeconds || 0, progress.durationSeconds || 0);
}
}, [reportProgress, progress.durationSeconds, course?._id]);

const handleResume = () => {
setUseResume(true);
};

const handleStartOver = () => {
resetProgress();
setUseResume(false);
setPlayerKey((k) => k + 1);
};
Comment on lines +60 to +68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Vidstack react MediaPlayer clipStartTime prop reactive change after mount seek

💡 Result:

In the Vidstack React MediaPlayer, the clipStartTime prop is intended to define a playback range boundary [1]. While you can change the clipStartTime prop after the component has mounted by updating the state passed to the MediaPlayer, this does not automatically trigger a seek operation to that new start time [2]. To achieve a reactive update where the player seeks to the new start time when the clipStartTime prop changes, you must manually coordinate the seek operation using the player instance [2][3]. Implementation Strategy: 1. Access the Player Instance: Use the useMediaPlayer hook within a child component of the MediaPlayer to retrieve the player instance [3][4]. 2. Detect Changes: Use a useEffect hook to watch for changes to the clipStartTime variable [5]. 3. Trigger Seek: Inside the effect, use the player instance or a MediaRemoteControl to set the currentTime to the new clipStartTime [3]. Example approach: import { useMediaPlayer, useEffect } from '@vidstack/react'; function ClipController({ newStartTime }) { const player = useMediaPlayer; useEffect( => { if (player) { // Seek to the new start time player.currentTime = newStartTime; } }, [newStartTime, player]); return null; } Note that Vidstack includes internal logic (such as boundTime) that constrains playback within the range defined by clipStartTime and clipEndTime [1][6]. If you are building complex trimming interfaces, be aware that these props fundamentally alter the player's perception of the timeline and duration [2][6]. In some scenarios, manual intervention via event listeners like onMediaSeekRequest may be necessary to bypass default bounding behavior if it conflicts with your specific UI requirements [7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='app/dashboard/courses/[courseId]/CourseDetailPageClient.jsx'

# Show the relevant region with line numbers.
sed -n '1,220p' "$file" | nl -ba | sed -n '1,220p'

# Find player-related state and props in the file.
echo
rg -n "playerKey|useResume|resumeTime|clipStartTime|currentTime|MediaPlayer|handleResume|handleStartOver|resetProgress" "$file"

Repository: Deen-Bridge/dnb-frontend

Length of output: 202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='app/dashboard/courses/[courseId]/CourseDetailPageClient.jsx'

echo '--- relevant file section ---'
sed -n '1,240p' "$file" | cat -n

echo
echo '--- player/start-time references ---'
rg -n "playerKey|useResume|resumeTime|clipStartTime|currentTime|MediaPlayer|handleResume|handleStartOver|resetProgress|startTime" "$file"

Repository: Deen-Bridge/dnb-frontend

Length of output: 11269


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='components/atoms/dashboard/vid-player-box.jsx'

echo '--- file outline ---'
wc -l "$file"
echo
sed -n '1,260p' "$file" | cat -n
echo
echo '--- startTime / MediaPlayer references ---'
rg -n "startTime|clipStartTime|currentTime|MediaPlayer|useMediaPlayer|seek|playerKey|key=" "$file"

Repository: Deen-Bridge/dnb-frontend

Length of output: 3119


Resume needs a remount or explicit seek

handleResume only flips useResume, but VidPlayerBox passes startTime into MediaPlayer as clipStartTime. The player stays mounted, so Resume won’t jump to resumeTime; Start Over works because it increments playerKey. Bump playerKey in handleResume too, or seek the player ref when enabling resume.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/dashboard/courses/`[courseId]/CourseDetailPageClient.jsx around lines 60
- 68, Update handleResume to remount the player or explicitly seek through the
player ref when enabling resume, ensuring VidPlayerBox applies resumeTime as
clipStartTime. Prefer incrementing playerKey consistently with handleStartOver
if no seek API is already available.


const userReview = useMemo(() => {
if (!user?._id || !course?.reviews) return null;
return course.reviews.find(
(r) => r.user?._id === user._id || r.user?.id === user._id
);
}, [user, course?.reviews]);

// Check if creator has wallet connected
const creatorHasWallet = course?.createdBy?.stellarWallet?.publicKey;

// Handle opening the payment modal
const handlePurchaseCourse = () => {
if (!user?._id) {
toast.error("Please sign in to purchase this course.");
Expand All @@ -51,7 +84,6 @@ export default function CourseDetailClient({ course }) {
setShowPaymentModal(true);
};

// Handle successful payment
const handlePaymentSuccess = async (result) => {
setPurchased(true);
if (user?._id) {
Expand Down Expand Up @@ -86,7 +118,6 @@ export default function CourseDetailClient({ course }) {
return null;
}

// Check if user can access the course
const canAccess =
hasCourse || purchased || user?._id === course.createdBy?._id;

Expand All @@ -97,12 +128,77 @@ export default function CourseDetailClient({ course }) {
</h2>

{canAccess ? (
<div className="w-full aspect-video mb-8 rounded-xl">
<VidPlayerBox data={course} />
<div className="w-full mb-8 rounded-xl">
{progress.percent > 0 && !progress.completed && resumeLabel && !useResume && (
<div className="mb-4 p-4 bg-accent/10 border border-accent/30 rounded-xl flex flex-col sm:flex-row items-center justify-between gap-3">
<div className="flex items-center gap-3">
<Play className="h-5 w-5 text-accent" />
<div>
<p className="font-semibold text-sm">{resumeLabel}</p>
<Progress value={progress.percent} className="w-48 h-1.5 mt-1" />
</div>
</div>
<div className="flex gap-2">
<Button
round
className="bg-accent hover:bg-accent/90 text-white text-sm font-semibold px-4"
onClick={handleResume}
>
<Play className="h-4 w-4 mr-1" />
Resume
</Button>
<Button
round
outlined
className="text-sm px-4"
onClick={handleStartOver}
>
<RotateCcw className="h-4 w-4 mr-1" />
Start Over
</Button>
</div>
</div>
)}

{progress.completed && (
<div className="mb-4 p-3 bg-green-50 border border-green-200 rounded-xl flex items-center gap-2">
<span className="text-green-600 font-semibold text-sm">
✅ Course Completed
</span>
<Button
round
outlined
className="text-xs ml-auto px-3 py-1"
onClick={handleStartOver}
>
<RotateCcw className="h-3 w-3 mr-1" />
Watch Again
</Button>
</div>
)}

<div className="w-full aspect-video rounded-xl overflow-hidden">
<VidPlayerBox
key={playerKey}
data={course}
startTime={effectiveStartTime}
onTimeUpdate={handleTimeUpdate}
onEnded={handleEnded}
/>
</div>

{progress.percent > 0 && (
<div className="mt-3">
<div className="flex justify-between text-xs text-muted-foreground mb-1">
<span>{progress.percent}% complete</span>
<span>{formatTime(progress.positionSeconds)} / {formatTime(progress.durationSeconds)}</span>
</div>
<Progress value={progress.percent} className="h-1.5" />
</div>
)}
</div>
) : (
<div className="w-full aspect-video mb-8 relative rounded-xl overflow-hidden">
{/* Blurred thumbnail preview */}
<div className="absolute inset-0 bg-gradient-to-br from-accent/20 to-highlight/20 backdrop-blur-sm z-10 flex items-center justify-center">
<div className="text-center space-y-4 p-8 bg-background/90 rounded-xl shadow-2xl">
<h3 className="text-2xl font-bold">🔒 Course Locked</h3>
Expand Down Expand Up @@ -214,18 +310,15 @@ export default function CourseDetailClient({ course }) {
</div>
)}
</div>
{/* Right Column: Pricing & Actions */}
<aside className="space-y-4">
<div className="border rounded-xl p-6 shadow-lg bg-card space-y-6 sticky top-4">
{/* Price Section */}
<div className="text-center space-y-2">
<p className="text-sm text-muted-foreground">Course Price</p>
<div className="text-5xl font-bold text-accent">
{course.price === 0 ? "Free" : `$${course.price}`}
</div>
</div>

{/* Purchase/Access Button */}
{!hasCourse && !purchased && user?._id !== course.createdBy?._id ? (
<div className="space-y-2">
<Button
Expand Down Expand Up @@ -254,7 +347,6 @@ export default function CourseDetailClient({ course }) {
</div>
) : null}

{/* Course Stats */}
<div className="space-y-3 pt-4 border-t">
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
Expand All @@ -281,7 +373,6 @@ export default function CourseDetailClient({ course }) {
</aside>
</div>

{/* Reviews Section */}
<div className="px-2 sm:px-10 mt-12">
<h2 className="text-3xl font-semibold mb-6">Reviews</h2>
<ReviewsSection
Expand All @@ -292,7 +383,6 @@ export default function CourseDetailClient({ course }) {
/>
</div>

{/* Stellar Payment Modal */}
<PaymentModal
isOpen={showPaymentModal}
onClose={() => setShowPaymentModal(false)}
Expand Down
3 changes: 3 additions & 0 deletions app/dashboard/courses/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { Card, CardContent } from "@/components/ui/card";
import { fetchCourses } from "@/lib/actions/courses/fetch-courses";
import { getBookmarkedCourses } from "@/lib/actions/courses/bookmark-course";
import useAuth from "@/hooks/useAuth";
import { useAllCourseProgress } from "@/hooks/useCourseProgress";
import NetworkErrorComp from "@/components/molecules/errors/NetworkError";

export default function CoursesPage() {
const { user } = useAuth();
const { progressMap } = useAllCourseProgress();
const [courses, setCourses] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
Expand Down Expand Up @@ -125,6 +127,7 @@ export default function CoursesPage() {
<CourseCard
key={course._id}
course={course}
progress={progressMap[course._id]}
onBookmarkChange={(isBookmarked) => {
if (showBookmarks && !isBookmarked) {
setCourses(courses.filter((c) => c._id !== course._id));
Expand Down
38 changes: 37 additions & 1 deletion components/atoms/dashboard/vid-player-box.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,43 @@ import {
MediaProvider,
Poster,
Track,
useMediaPlayer,
} from '@vidstack/react';
import {
DefaultVideoLayout,
defaultLayoutIcons,
} from '@vidstack/react/player/layouts/default';
import { useEffect, useRef } from 'react';

const VidPlayerBox = ({ data }) => {
function PlayerProgressTracker({ onTimeUpdate, onEnded }) {
const player = useMediaPlayer();
const lastReportRef = useRef(0);

useEffect(() => {
if (!player) return;

const timeSub = player.on('time-update', (e) => {
if (onTimeUpdate) {
onTimeUpdate(e.currentTime, e.duration);
}
});

const endedSub = player.on('ended', () => {
if (onEnded) {
onEnded();
}
});
Comment on lines +32 to +36

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The onEnded callback carries no duration, so completion-on-end can silently fail. The player knows the authoritative duration when ended fires but discards it; the consumer therefore has to reconstruct it from React state, and that value can still be 0, in which case isCompleted(0, 0) returns false and the finished course is never marked complete.

  • components/atoms/dashboard/vid-player-box.jsx#L32-L36: invoke onEnded(player.state.duration) so the real duration reaches the consumer.
  • app/dashboard/courses/[courseId]/CourseDetailPageClient.jsx#L54-L58: accept the duration argument, prefer it over progress.durationSeconds, and fall back to an explicit completion path rather than reportProgress(0, 0) when no duration is available.
📍 Affects 2 files
  • components/atoms/dashboard/vid-player-box.jsx#L32-L36 (this comment)
  • app/dashboard/courses/[courseId]/CourseDetailPageClient.jsx#L54-L58
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/atoms/dashboard/vid-player-box.jsx` around lines 32 - 36, The
ended handler in components/atoms/dashboard/vid-player-box.jsx lines 32-36 must
pass player.state.duration to onEnded. Update the onEnded consumer in
app/dashboard/courses/[courseId]/CourseDetailPageClient.jsx lines 54-58 to
accept that duration, prefer it over progress.durationSeconds, and use an
explicit completion path when no duration is available instead of calling
reportProgress(0, 0).


return () => {
timeSub();
endedSub();
};
}, [player, onTimeUpdate, onEnded]);

return null;
}
Comment on lines +19 to +45

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Progress reporting is unthrottled end-to-end. The PR aims to "throttle progress writes", but the only throttle is the 15s gate on the backend PUT. Vidstack's time-update fires several times per second and every one of those events currently causes a React state update, a synchronous JSON.stringify + localStorage.setItem, and — because the state change invalidates the parent's handleEnded — a full teardown and re-attach of both player event subscriptions. The intended throttle already has a home: the unused lastReportRef.

  • components/atoms/dashboard/vid-player-box.jsx#L19-L45: use lastReportRef to drop time-update events that arrive within ~1s of the previous forwarded one, so onTimeUpdate is called at most once per second. Also drop onEnded from the effect dep array (keep it in a ref) so the subscriptions attach once per player rather than once per tick.
  • hooks/useCourseProgress.js#L179-L192: make setProgress and writeLocalProgress conditional — skip both when Math.floor(positionSeconds) and the derived percent are unchanged from currentDataRef.current, so idle ticks cost nothing.
📍 Affects 2 files
  • components/atoms/dashboard/vid-player-box.jsx#L19-L45 (this comment)
  • hooks/useCourseProgress.js#L179-L192
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/atoms/dashboard/vid-player-box.jsx` around lines 19 - 45, Throttle
PlayerProgressTracker time-update forwarding to at most once per second using
lastReportRef, and store onEnded in a ref so the effect does not resubscribe on
callback changes; update the effect dependencies accordingly while preserving
cleanup. In hooks/useCourseProgress.js lines 179-192, conditionally skip
setProgress and writeLocalProgress when the floored positionSeconds and derived
percent match currentDataRef.current.


const VidPlayerBox = ({ data, startTime, onTimeUpdate, onEnded }) => {
const textTracks = data?.subtitles?.length
? data.subtitles
: [];
Expand All @@ -29,6 +59,7 @@ const VidPlayerBox = ({ data }) => {
playsInline
title={data?.title}
poster={data?.thumbnail}
clipStartTime={startTime || undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Vidstack clipStartTime clipEndTime reported duration timeline behavior

💡 Result:

In Vidstack, clipStartTime and clipEndTime are properties used to shorten media by defining the boundaries of playback [1][2]. Their behavior regarding the timeline, duration, and state management is defined as follows: Duration Behavior: When clipStartTime and clipEndTime are set, the player updates the media duration to match the new clipped length [1][2]. Seeking, progress tracking, and other time-based UI components (such as chapters) are recalculated to reflect this clipped duration [1]. For example, if a 60-second video is clipped to start at 30 seconds and end at 60 seconds, the player's reported duration becomes 30 seconds, and seeking to 30s moves the playhead to the end of the media [1]. Timeline and State Behavior: - Internal URI Fragments: Vidstack internally uses Media URI Fragments (e.g., /video.mp4#t=30,60) to efficiently load and constrain the media between the specified start and end times [1][2]. - Seeking: Seeking operations are relative to the clipped duration [1]. - Reporting: The duration property (available on the player component) reflects the total length of the clipped segment [1][3]. - Implementation Notes: While Vidstack handles the clipping logic, there have been reported edge cases in specific versions where the UI timeline or progress thumb might behave unexpectedly (e.g., getting "stuck" or resetting) depending on how and when the clipping properties are updated during playback [4]. Usage: These properties can be passed directly as attributes or props to the MediaPlayer component [2]: - If you require looping within a custom range rather than permanent clipping, Vidstack does not provide a built-in range-loop feature; this is typically handled by subscribing to time updates and programmatically resetting currentTime when the end time is reached [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="components/atoms/dashboard/vid-player-box.jsx"

echo "== outline =="
ast-grep outline "$file" --view expanded || true

echo
echo "== relevant lines =="
nl -ba "$file" | sed -n '1,180p'

Repository: Deen-Bridge/dnb-frontend

Length of output: 418


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="components/atoms/dashboard/vid-player-box.jsx"

echo "== lines 1-140 =="
awk 'NR>=1 && NR<=140 { printf("%4d  %s\n", NR, $0) }' "$file"

Repository: Deen-Bridge/dnb-frontend

Length of output: 2587


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "onTimeUpdate|calcPercent|reportProgress|VidPlayerBox" components . --glob '!**/node_modules/**'

Repository: Deen-Bridge/dnb-frontend

Length of output: 3238


🏁 Script executed:

#!/bin/bash
set -euo pipefail

files=(
  "app/dashboard/courses/[courseId]/CourseDetailPageClient.jsx"
  "hooks/useCourseProgress.js"
)

for file in "${files[@]}"; do
  echo "== $file =="
  awk 'NR>=1 && NR<=260 { printf("%4d  %s\n", NR, $0) }' "$file"
  echo
done

Repository: Deen-Bridge/dnb-frontend

Length of output: 20434


🌐 Web query:

Vidstack clipStartTime currentTime duration time-update relative to clipped duration docs

💡 Result:

In Vidstack Player, the relationship between playback time and clipping is managed by distinguishing between the "real" playback time (raw provider time) and the "clipped" playback time [1][2]. When using clipStartTime and clipEndTime, Vidstack automatically adjusts the player state to reflect the clipped duration [3][4]. Here is how the relevant time properties behave: currentTime: The currentTime property represents the playback time relative to the clipped duration [1][5]. If you have a 60-second video and set a clipStartTime of 30 seconds, the player will treat the 30-second mark of the source as 0 seconds in the clipped timeline [1][3]. Setting this value will seek the media to the corresponding position within the clipped window [1][5]. time-update: The time-update event is dispatched when the playback time changes [2]. The detail provided in this event contains the currentTime value, which is relative to the clipped duration [2][6]. This is the recommended event to listen to if you want to track the playback position within the clipped range [2]. time-change: If you require the "real" or "raw" time (the actual position in the original source media without accounting for clipping), you should listen to the time-change event instead [2]. The time-change event provides the absolute provider time [2]. Underlying Logic: The internal state management calculates currentTime using the following logic [1]: If clipStartTime > 0, currentTime is derived by subtracting the clipStartTime from the realCurrentTime (clamped to the clipped duration) [1]. The duration property is automatically updated to match the clipped length (e.g., if you clip a 1-minute video to 30 seconds, the duration will be 30 seconds) [3][4]. This approach allows you to work with clipped media as if it were a standalone file, while the underlying MediaRemoteControl or state hooks provide the necessary tools to manipulate playback within those bounds [7][8][3].

Citations:


components/atoms/dashboard/vid-player-box.jsx:62 — clipStartTime is the wrong primitive for resume here. Vidstack reports time-update.currentTime and duration relative to the clipped window, so reportProgress() will store a shortened duration and undercount course progress after resume. Seek to startTime once the player is ready instead of clipping the source.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/atoms/dashboard/vid-player-box.jsx` at line 62, Update the video
player setup in VidPlayerBox so clipStartTime no longer receives startTime or
clips the source. Preserve the original media duration, and seek to startTime
once the player is ready using the existing player-ready lifecycle and refs;
ensure reportProgress() continues receiving the full duration while playback
resumes at the requested offset.

>
<MediaProvider>
<Poster className="vds-poster" />
Expand All @@ -41,6 +72,11 @@ const VidPlayerBox = ({ data }) => {
thumbnails={data?.thumbnails || undefined}
icons={defaultLayoutIcons}
/>

<PlayerProgressTracker
onTimeUpdate={onTimeUpdate}
onEnded={onEnded}
/>
</MediaPlayer>
</div>
);
Expand Down
Loading
Loading