---
phase: 01-backing-track-playback-monitoring
type: execute
---

<objective>
Implement real-time playback monitoring for the backing track player by adding 500ms polling of the native client and displaying current position and duration in M:SS format.

Purpose: Complete the player's time display functionality, matching the legacy web implementation's polling pattern while using modern React hooks. This establishes the monitoring foundation that Phase 2 (seek controls) will build upon.

Output: Backing track player with live-updating time displays showing current position and total duration while track plays.
</objective>

<execution_context>
@./.claude/get-shit-done/workflows/execute-phase.md
@./.claude/get-shit-down/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-backing-track-playback-monitoring/1-CONTEXT.md
@jam-ui/src/components/client/JKSessionBackingTrackPlayer.js

**Codebase conventions:**
- React functional components with hooks (React 16.13.1)
- camelCase for functions and variables
- Single quotes for strings (Prettier enforced)
- 2-space indentation

**Legacy implementation reference:**
- web/app/assets/javascripts/playbackControls.js: 500ms polling via setTimeout (lines 301-337)
- web/app/assets/javascripts/utils.js: prettyPrintSeconds() time formatting (lines 885-906)
- jamClient methods: SessionCurrrentPlayPosMs(), SessionGetTracksPlayDurationMs(), isSessionTrackPlaying()

**Current state:**
- JKSessionBackingTrackPlayer.js has currentTime and duration state hardcoded to "0:00" (lines 20-21)
- Play/pause/stop controls work via jamClient (lines 34-58)
- UI structure already in place with time displays (lines 207, 217 for popup; lines 302, 312 for modal)

**Vision from CONTEXT.md:**
- Functional parity with legacy web (500ms polling, accurate time display)
- UI layout: Current time (M:SS) on left of slider, duration (M:SS) on right
- No padding on single-digit minutes (1:02 not 01:02)
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add formatTime utility function</name>
  <files>jam-ui/src/components/client/JKSessionBackingTrackPlayer.js</files>
  <action>Add a formatTime utility function to convert milliseconds to M:SS format (matching legacy web prettyPrintSeconds behavior). Place it inside the component before the useEffect hooks.

Implementation:
- Accept milliseconds as parameter
- Convert to seconds (Math.floor(ms / 1000))
- Calculate minutes: Math.floor(seconds / 60)
- Calculate remaining seconds: seconds % 60
- Pad seconds with "0" if less than 10 (e.g., "1:05" not "1:5")
- NO padding on minutes (single digit is fine: "1:02" not "01:02")
- Return formatted string: `${minutes}:${seconds.toString().padStart(2, '0')}`

Example outputs:
- formatTime(62000) → "1:02"
- formatTime(313000) → "5:13"
- formatTime(3605000) → "60:05"

Do NOT implement hours formatting (tracks are < 60 minutes).</action>
  <verify>Add console.log tests:
- formatTime(62000) should output "1:02"
- formatTime(313000) should output "5:13"
- formatTime(0) should output "0:00"</verify>
  <done>formatTime function exists, converts milliseconds to M:SS format with proper padding (seconds only)</done>
</task>

<task type="auto">
  <name>Task 2: Add playback monitoring with 500ms polling</name>
  <files>jam-ui/src/components/client/JKSessionBackingTrackPlayer.js</files>
  <action>Add useEffect hook for playback monitoring with 500ms polling interval matching legacy web implementation (web/app/assets/javascripts/playbackControls.js lines 301-337).

Implementation:
- Add useEffect that runs when isPlaying changes
- When isPlaying is true, start polling:
  - Use setInterval (not setTimeout recursion) with 500ms interval
  - In interval callback:
    1. Check if jamClient exists (guard against undefined)
    2. Call jamClient.SessionCurrrentPlayPosMs() → current position in ms
    3. Call jamClient.SessionGetTracksPlayDurationMs() → total duration in ms
    4. Call jamClient.isSessionTrackPlaying() → boolean for playing state
    5. Update setCurrentTime(formatTime(positionMs))
    6. Update setDuration(formatTime(durationMs))
    7. Sync setIsPlaying(isPlaying) if state changed
- When isPlaying is false, clear the interval
- Return cleanup function that clears interval

Error handling:
- Wrap jamClient calls in try/catch
- On error, log to console but don't crash
- Continue polling even if one poll fails

Dependencies: [isPlaying, jamClient, backingTrack]

IMPORTANT: Use setInterval (not setTimeout recursion) for consistent 500ms timing. Store intervalId in a variable to clear on cleanup.</action>
  <verify>Check console logs show:
1. When play clicked: Polling starts, position/duration update every 500ms
2. When pause clicked: Polling stops, interval cleared
3. When stop clicked: Polling stops, position resets to 0:00
4. No errors logged during playback</verify>
  <done>Playback monitoring polls jamClient every 500ms when playing, updates currentTime and duration state with formatted values, cleans up interval on pause/stop</done>
</task>

<task type="auto">
  <name>Task 3: Initialize duration on track load</name>
  <files>jam-ui/src/components/client/JKSessionBackingTrackPlayer.js</files>
  <action>Modify the existing useEffect (lines 25-32) that runs when track opens to fetch and set initial duration immediately, not waiting for playback to start.

Current code:
```javascript
useEffect(() => {
  if (isOpen && backingTrack) {
    setIsPlaying(false);
    setCurrentTime('0:00');
    setDuration('0:00');
  }
}, [isOpen, backingTrack]);
```

Change to:
```javascript
useEffect(() => {
  if (isOpen && backingTrack && jamClient) {
    setIsPlaying(false);
    setCurrentTime('0:00');

    // Fetch and set duration immediately when track loads
    try {
      const durationMs = jamClient.SessionGetTracksPlayDurationMs();
      setDuration(formatTime(durationMs));
    } catch (error) {
      console.error('Error fetching track duration:', error);
      setDuration('0:00');
    }
  }
}, [isOpen, backingTrack, jamClient]);
```

This ensures the duration appears immediately when the player opens, matching the UI in the reference screenshot where "5:13" is visible before playback starts.</action>
  <verify>Open backing track player without playing:
- Duration displays actual track length (e.g., "5:13") immediately
- Current time shows "0:00"
- No errors in console</verify>
  <done>Duration fetched and displayed when track loads (before play starts), matching legacy web behavior</done>
</task>

</tasks>

<verification>
Before declaring phase complete:
- [ ] Open backing track player, verify duration shows immediately (e.g., "5:13")
- [ ] Click play, verify current time updates every ~500ms (1:00 → 1:01 → 1:02)
- [ ] Verify time format matches reference: M:SS (no leading zero on minutes)
- [ ] Click pause, verify polling stops (time freezes)
- [ ] Click play again, verify polling resumes from paused position
- [ ] Click stop, verify time resets to "0:00"
- [ ] Check browser console for errors (should be none)
- [ ] Verify both modal and popup versions work identically
</verification>

<success_criteria>

- formatTime utility converts milliseconds to M:SS format correctly
- Playback monitoring polls jamClient every 500ms when playing
- Current time display updates smoothly during playback
- Duration displays immediately when track loads
- Polling stops cleanly when paused/stopped
- No console errors during playback
- Functional parity with legacy web monitoring behavior
</success_criteria>

<output>
After completion, create `.planning/phases/01-backing-track-playback-monitoring/01-01-SUMMARY.md`:

# Phase 1 Plan 1: Backing Track Playback Monitoring Summary

**Real-time backing track playback monitoring with 500ms polling and M:SS time display**

## Accomplishments

- Added formatTime utility for milliseconds → M:SS conversion
- Implemented 500ms polling with setInterval matching legacy web pattern
- Wired up currentTime and duration displays to live jamClient data
- Duration fetches immediately on track load (before play)

## Files Created/Modified

- `jam-ui/src/components/client/JKSessionBackingTrackPlayer.js` - Added formatTime, polling useEffect, duration initialization

## Decisions Made

- Used setInterval (not setTimeout recursion) for consistent 500ms timing
- Polling runs only when isPlaying=true to avoid unnecessary jamClient calls
- Error handling: log errors but continue polling (don't crash on transient failures)

## Issues Encountered

(None expected - straightforward polling implementation)

## Next Phase Readiness

Phase 2 (Backing Track Seek Controls) can now proceed. The monitoring useEffect will need slight modification to support seeking:
- When user drags slider, pause polling briefly
- After seek, resume polling from new position
- This is handled in Phase 2, not here
</output>
