docs(04-01): create legacy JamTrack implementation documentation

- Documented jQuery dialog patterns with search/pagination
- Captured 8-state download/sync state machine (CoffeeScript)
- Explained Reflux store patterns and WebSocket subscriptions
- Mapped REST API endpoints and integration points
- Documented fqId format, JMEP support, mixdown selection logic
This commit is contained in:
Nuwan 2026-01-14 21:19:21 +05:30
parent 794600678e
commit b7208d945c
1 changed files with 264 additions and 0 deletions

View File

@ -0,0 +1,264 @@
# JamTrack Legacy Implementation Reference
This document captures the legacy jQuery/CoffeeScript JamTrack implementation patterns discovered in the web project, serving as a reference for the React migration in jam-ui.
## 1. Legacy Dialog Patterns
### openJamTrackDialog.js
**Location:** `web/app/assets/javascripts/dialog/openJamTrackDialog.js`
**Purpose:** jQuery-based dialog for JamTrack selection with search and pagination
**Key Components:**
- **Search functionality** (lines 23-85):
- User input via React JamTrackAutoComplete component embedded in jQuery dialog
- Cookie persistence for search state (`jamtrack_session_search`)
- Search types: 'user-input', autocomplete-driven
- Shows search UI only if user has > 10 purchased JamTracks
- **Pagination** (lines 77-82):
- 10 items per page (`perPage = 10`)
- Uses `JK.Paginator.create()` utility
- Reads total count from response header: `jqXHR.getResponseHeader('total-entries')`
- **Initialization pattern:**
```javascript
context.JK.OpenJamTrackDialog = function(app) {
// Dialog state variables
var showing = false;
var $dialog = null;
var sampleRate = null; // Set from jamClient.GetSampleRate()
// Template references
var $templateOpenJamTrackRow = null;
// Returns API with show/hide methods
return {
show: function() { /* ... */ },
hide: function() { /* ... */ },
search: function(searchType, searchData) { /* ... */ }
};
}
```
- **Data flow:**
1. Dialog opens via `app.layout.showDialog('open-jam-track-dialog')`
2. User selects JamTrack from list
3. Dialog closes, fires `EVENTS.DIALOG_CLOSED` event
4. Callback receives `{canceled: false, result: {jamTrack: selectedJamTrack}}`
5. Calls `loadJamTrack(jamTrack)` (session.js:2716)
**REST API Integration:**
- Fetches purchased JamTracks with pagination
- Endpoint called via `getPurchasedJamTracks(offset)` helper
- Response headers include `total-entries` for pagination
## 2. Download/Sync State Machine
### download_jamtrack.js.coffee
**Location:** `web/app/assets/javascripts/download_jamtrack.js.coffee`
**Purpose:** 8-state CoffeeScript state machine managing JamTrack download, packaging, and synchronization
**State Machine (lines 59-68):**
| State | Name | Purpose | Leaf Node |
|-------|------|---------|-----------|
| `no_client` | 'no-client' | Browser mode (native client not available) | Yes |
| `synchronized` | 'synchronized' | JamTrack on disk, keys fetched, ready to play | Yes |
| `packaging` | 'packaging' | Server creating mixdown package | No |
| `downloading` | 'downloading' | Downloading package from server to disk | No |
| `keying` | 'keying' | Fetching encryption keys (max 10s timeout) | No |
| `initial` | 'initial' | Starting state, determining next action | No |
| `quiet` | 'quiet' | Hidden state before activation | No |
| `errored` | 'errored' | Error occurred, show error message | Yes |
**Key Operations:**
1. **Initialization** (lines 90-99):
```coffeescript
init: () =>
@active = true
if !gon.isNativeClient
this.transition(@states.no_client) # Browser fallback
else
this.transition(@states.initial) # Start state machine
```
2. **State transitions** (checkState method, not shown in excerpt):
- Queries `jamClient.JamTrackGetTrackDetail(fqId)` for sync status
- Returns: `{key_state: string, version: number}`
- Decides next state based on client-reported status
3. **Download flow:**
- `packaging` → Server preparing mixdown
- `downloading``JamTrackDownload(jamTrackId, mixdownId, userId, progressCallback, successCallback, failCallback)`
- `keying``JamTrackKeysRequest()` to fetch encryption keys
- `synchronized` → Ready to play
4. **Progress tracking** (lines 45-52):
- `@startTime`, `@attempts`, `@tracked` for metrics
- `@currentPackagingStep` / `@totalSteps` for server packaging progress
- Fires `EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED` on transitions
5. **Error handling:**
- `@errorReason` and `@errorMessage` set before transitioning to `errored`
- Timeout states: SIGNING_TIMEOUT, QUEUED_TIMEOUT, QUIET_TIMEOUT
- Ajax abort methods for cleanup
**Integration with session.js:**
- Created in `loadJamTrack()` (session.js:2731): `new context.JK.DownloadJamTrack(app, jamTrack, 'large')`
- Listens for `JAMTRACK_DOWNLOADER_STATE_CHANGED` event
- When `synchronized` state reached, widget hidden and playback initiated
## 3. Store Patterns (Reflux)
### JamTrackStore.js.coffee
**Location:** `web/app/assets/javascripts/react-components/stores/JamTrackStore.js.coffee`
**Purpose:** Reflux store managing mixdown synchronization state
**Key Functionality:**
1. **pickMyPackage() logic** (lines 30-43):
- Selects compatible mixdown package for current client
- Criteria: `file_type == 'ogg' && encrypt_type == 'jkz' && sample_rate == @sampleRate`
- `@sampleRate` comes from `jamClient.GetSampleRate()` (44 or 48 kHz)
- Sets `mixdown.myPackage` property for each mixdown
2. **WebSocket subscription pattern** (lines 45-78):
- Subscribes to mixdown packaging progress: `context.JK.SubscriptionUtils.subscribe('mixdown', mixdown_package.id)`
- Listens for `SUBSCRIBE_NOTIFICATION` events
- Updates `signing_state`, `packaging_steps`, `current_packaging_step` from WebSocket data
- Unsubscribes when `signing_state == 'SIGNED'` (package ready)
3. **Mixdown state tracking:**
- `signing_state` values: SIGNING_TIMEOUT, QUEUED_TIMEOUT, QUIET_TIMEOUT, ERROR, SIGNED
- `packaging_steps` (total) and `current_packaging_step` for progress bar
- Calls `@reportError(mixdown)` on timeout/error states
4. **Store lifecycle:**
- `init()`: Registers with AppStore to get `@app` reference
- `manageWatchedMixdowns()`: Subscribe to active, unsubscribe from complete
- Clears all subscriptions when JamTrack changes or unmounts
### JamTrackMixdownStore (referenced but not shown)
- Handles mixdown CRUD operations
- Connected via `listenables: [JamTrackActions, JamTrackMixdownActions]`
## 4. REST API Endpoints
Based on route analysis and code references:
| Method | Endpoint | Purpose |
|--------|----------|---------|
| POST | `/api/sessions/{sessionId}/jam_tracks/{jamTrackId}/open` | Open JamTrack in session |
| POST | `/api/jamtracks/{id}/mixdowns/active` | Set active mixdown for JamTrack |
| POST | `/api/mixdowns/` | Create new mixdown |
| PUT | `/api/mixdowns/{id}` | Edit existing mixdown |
| DELETE | `/api/mixdowns/{id}` | Delete mixdown |
| POST | `/api/mixdowns/{id}/enqueue` | Enqueue mixdown for packaging |
| GET | `/api/jamtracks/autocomplete` | Autocomplete search for JamTracks |
**Response patterns:**
- Pagination: `total-entries` header on list responses
- Per-page: 10 items default
- Search: Cookie-persisted search state
## 5. Integration Points
### session.js loadJamTrack() function
**Location:** `web/app/assets/javascripts/session.js` (lines 2716-2778)
**Flow:**
1. **Recording conflict check** (lines 2693-2700):
- Blocks JamTrack opening if actively recording
- Shows notification: "You can't open a jam track while creating a recording"
2. **Dialog interaction** (lines 2702-2713):
- Opens dialog: `app.layout.showDialog('open-jam-track-dialog')`
- Listens for `EVENTS.DIALOG_CLOSED` event
- Checks `!data.canceled && data.result.jamTrack`
- Calls `loadJamTrack(data.result.jamTrack)` on selection
3. **DownloadJamTrack widget** (lines 2723-2777):
- Cleans up previous widget if exists (lines 2723-2729)
- Creates: `new context.JK.DownloadJamTrack(app, jamTrack, 'large')`
- Listens for `JAMTRACK_DOWNLOADER_STATE_CHANGED` event
- Appends widget to `$otherAudioContainer` in session UI
4. **Playback initiation on synchronized** (lines 2736-2770):
```javascript
if(data.state == downloadJamTrack.states.synchronized) {
// Hide download widget
downloadJamTrack.root.remove();
downloadJamTrack.destroy();
// Stop any existing playback
context.jamClient.JamTrackStopPlay();
// Build fqId: {jamTrackId}-{sampleRate}
var sampleRate = context.jamClient.GetSampleRate(); // 44 or 48
var sampleRateForFilename = sampleRate == 48 ? '48' : '44';
var fqId = jamTrack.id + '-' + sampleRateForFilename;
// Load JMEP (Jam Enhancement Package) if present
if(jamTrack.jmep) {
context.jamClient.JamTrackLoadJmep(fqId, jamTrack.jmep);
}
// "JamTrackPlay" means "load" in this API
var result = context.jamClient.JamTrackPlay(fqId);
if(result) {
playJamTrack(jamTrack.id); // Start actual playback
} else {
app.notify({ title: "JamTrack Can Not Open", ... });
}
}
```
5. **JMEP (Jam Enhancement Package):**
- Tempo/pitch modifications
- Loaded via `JamTrackLoadJmep(fqId, jmepData)` before playback
- Optional feature, checked with `if(jamTrack.jmep)`
6. **fqId format:**
- Fully-qualified ID: `{jamTrackId}-{sampleRate}`
- Sample rate: '44' or '48' based on client audio config
- Used for all jamClient JamTrack operations
**Event pattern:**
- `EVENTS.DIALOG_CLOSED`: Dialog selection callback
- `EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED`: State machine transitions
---
## Summary for React Migration
**Key patterns to preserve:**
1. **fqId format:** Always use `{jamTrackId}-{sampleRate}` for jamClient calls
2. **Mixdown selection:** pickMyPackage() logic filters by ogg/jkz/sample_rate
3. **State machine:** Track download/sync states for UX feedback
4. **WebSocket updates:** Subscribe to mixdown packaging progress
5. **JMEP support:** Load tempo/pitch modifications before playback
6. **Recording conflicts:** Block JamTrack opening during active recording
**Differences from Backing Track:**
- **No native file dialog:** JamTrack uses jQuery dialog with purchased track list
- **Download process:** Multi-state sync (packaging → downloading → keying → synchronized)
- **Mixdown selection:** User chooses stem configuration (full track vs. specific mixes)
- **Encryption keys:** Requires separate `JamTrackKeysRequest()` call after download
- **Real-time packaging:** WebSocket updates during server-side mixdown creation
**Complexity factors:**
- 8-state synchronization machine vs. simple file open
- Server-side packaging with progress tracking
- Mixdown package selection based on client capabilities
- Encryption key management
- JMEP (tempo/pitch) modification support