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:
parent
794600678e
commit
b7208d945c
|
|
@ -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
|
||||
Loading…
Reference in New Issue