From 5cb0b7083bd46074ab69d877f7ea3c7286d962ad Mon Sep 17 00:00:00 2001 From: Steve 'Cutter' Blades Date: Fri, 17 Apr 2026 11:51:32 -0400 Subject: [PATCH 1/2] fix(cdg-controls): Handle toggle race and allow test stub args --- eslint.config.mjs | 8 ++ .../cdg-controls/src/lib/controls.spec.ts | 90 +++++++++++++++++++ packages/cdg-controls/src/lib/controls.ts | 16 +++- 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 1a7a0ba..00d8831 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -46,6 +46,14 @@ export default [ files: ['**/*.{spec,test}.{ts,tsx,js,jsx}'], rules: { '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], }, }, ]; diff --git a/packages/cdg-controls/src/lib/controls.spec.ts b/packages/cdg-controls/src/lib/controls.spec.ts index ea0f89f..78ac2d4 100644 --- a/packages/cdg-controls/src/lib/controls.spec.ts +++ b/packages/cdg-controls/src/lib/controls.spec.ts @@ -147,6 +147,74 @@ class MockPlayerWithoutTempo } } +class MockPlayerWithDelayedPlay + extends EventTarget + implements ControlsPlayerAdapter +{ + private state: ControlsPlayerState = { + status: 'idle', + trackId: null, + currentTimeMs: 0, + durationMs: 0, + volume: 1, + playbackRate: 1, + pitchSemitones: 0, + }; + + plays = 0; + pauses = 0; + private resolvePlay: (() => void) | null = null; + + getState(): Readonly { + return this.state; + } + + async play(): Promise { + this.plays += 1; + + await new Promise((resolve) => { + this.resolvePlay = resolve; + }); + + this.setState({ status: 'playing' }); + } + + pause(): void { + this.pauses += 1; + this.setState({ status: 'paused' }); + } + + stop(): void { + this.setState({ status: 'ready', currentTimeMs: 0 }); + } + + seek(_args: { percentage: number }): void { + // Not needed for this test adapter. + } + + setVolume(_args: { value: number }): void { + // Not needed for this test adapter. + } + + setPlaybackRate(_args: { value: number }): void { + // Not needed for this test adapter. + } + + setPitchSemitones(_args: { value: number }): void { + // Not needed for this test adapter. + } + + flushPlayRequest(): void { + this.resolvePlay?.(); + this.resolvePlay = null; + } + + setState(nextPatch: Partial): void { + this.state = { ...this.state, ...nextPatch }; + this.dispatchEvent(new CustomEvent('statechange')); + } +} + describe('controls', () => { it('supports distributed controls via a shared model', async () => { const player = new MockPlayer(); @@ -350,4 +418,26 @@ describe('controls', () => { controls.dispose(); container.remove(); }); + + it('pauses on the next toggle even when play is still pending', async () => { + const player = new MockPlayerWithDelayedPlay(); + player.setState({ status: 'ready' }); + + const model = createControlsModel({ + options: { + player, + }, + }); + + const firstToggle = model.togglePlayPause(); + expect(player.plays).toBe(1); + + await model.togglePlayPause(); + expect(player.pauses).toBe(1); + + player.flushPlayRequest(); + await firstToggle; + + model.dispose(); + }); }); diff --git a/packages/cdg-controls/src/lib/controls.ts b/packages/cdg-controls/src/lib/controls.ts index 13fb013..5d6e8ac 100644 --- a/packages/cdg-controls/src/lib/controls.ts +++ b/packages/cdg-controls/src/lib/controls.ts @@ -244,6 +244,7 @@ class DefaultCdgControlsModel implements CdgControlsModel { (state: Readonly) => void >(); private state: ControlsViewState; + private playRequestInFlight = false; constructor({ player }: { player: ControlsPlayerAdapter }) { this.player = player; @@ -266,16 +267,25 @@ class DefaultCdgControlsModel implements CdgControlsModel { } async togglePlayPause(): Promise { - if (!this.state.isPlayable) { + const liveState = deriveViewState({ playerState: this.player.getState() }); + + if (!liveState.isPlayable) { return; } - if (this.state.isPlaying) { + if (liveState.isPlaying || this.playRequestInFlight) { + this.playRequestInFlight = false; this.player.pause(); return; } - await this.player.play(); + this.playRequestInFlight = true; + + try { + await this.player.play(); + } finally { + this.playRequestInFlight = false; + } } seekPercent({ percentage }: { percentage: number }): void { From ce76c2928fab786fb124df2acb038bc92518000a Mon Sep 17 00:00:00 2001 From: Steve 'Cutter' Blades Date: Fri, 17 Apr 2026 11:59:57 -0400 Subject: [PATCH 2/2] docs(links): Update published site URLs to cutterscrossing.com --- README.md | 18 +++++++++--------- packages/cdg-controls/README.md | 6 +++--- packages/cdg-controls/package.json | 2 +- packages/cdg-core/README.md | 4 ++-- packages/cdg-core/package.json | 2 +- packages/cdg-loader/README.md | 4 ++-- packages/cdg-loader/package.json | 2 +- packages/cdg-player/README.md | 4 ++-- packages/cdg-player/package.json | 2 +- packages/logger/package.json | 2 +- 10 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 350865f..ed04bbe 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ CDGPlayer provides browser karaoke playback libraries under the `@cxing/cdg-\*` package family. -**BREAKING CHANGE**: The legacy `CDGPlayer` and `CDGControls` monolithic packages have been deprecated and replaced with a modular package architecture. See the [migration guide](https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-migration-guide--docs) for details. +**BREAKING CHANGE**: The legacy `CDGPlayer` and `CDGControls` monolithic packages have been deprecated and replaced with a modular package architecture. See the [migration guide](https://cutterscrossing.com/?path=/docs/documentation-migration-guide--docs) for details. ## Packages @@ -23,14 +23,14 @@ Install the packages your app needs. ## Documentation -- [Getting started](https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-getting-started--documentation) -- [Migration guide](https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-migration-guide--docs) -- [Logger contract](https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-api-logger-contract--docs) -- [Loader contract](https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-api-loader-contract--documentation) -- [Player contract](https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-api-player-contract--documentation) -- [Controls contract](https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-api-controls-contract--documentation) -- [Framework-agnostic implementation guide](https://cutterbl.github.io/CDGPlayer/storybook-web/?path=/docs/examples-framework-agnostic-demo-implementation-guide--documentation) -- [React implementation guide](https://cutterbl.github.io/CDGPlayer/storybook-react/?path=/docs/examples-react-demo-implementation-guide--documentation) +- [Getting started](https://cutterscrossing.com/?path=/docs/documentation-getting-started--documentation) +- [Migration guide](https://cutterscrossing.com/?path=/docs/documentation-migration-guide--docs) +- [Logger contract](https://cutterscrossing.com/?path=/docs/documentation-api-logger-contract--docs) +- [Loader contract](https://cutterscrossing.com/?path=/docs/documentation-api-loader-contract--documentation) +- [Player contract](https://cutterscrossing.com/?path=/docs/documentation-api-player-contract--documentation) +- [Controls contract](https://cutterscrossing.com/?path=/docs/documentation-api-controls-contract--documentation) +- [Framework-agnostic implementation guide](https://cutterscrossing.com/storybook-web/?path=/docs/examples-framework-agnostic-demo-implementation-guide--documentation) +- [React implementation guide](https://cutterscrossing.com/storybook-react/?path=/docs/examples-react-demo-implementation-guide--documentation) ## Repository Contribution diff --git a/packages/cdg-controls/README.md b/packages/cdg-controls/README.md index 3c6dfd6..1bc5d1f 100644 --- a/packages/cdg-controls/README.md +++ b/packages/cdg-controls/README.md @@ -72,6 +72,6 @@ Your player adapter must provide: ## Docs -- Controls contract: https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-api-controls-contract--docs -- Framework-agnostic guide: https://cutterbl.github.io/CDGPlayer/storybook-web/?path=/docs/examples-framework-agnostic-demo-implementation-guide--docs -- React guide: https://cutterbl.github.io/CDGPlayer/storybook-react/?path=/docs/examples-react-demo-implementation-guide--docs +- Controls contract: https://cutterscrossing.com/?path=/docs/documentation-api-controls-contract--docs +- Framework-agnostic guide: https://cutterscrossing.com/storybook-web/?path=/docs/examples-framework-agnostic-demo-implementation-guide--docs +- React guide: https://cutterscrossing.com/storybook-react/?path=/docs/examples-react-demo-implementation-guide--docs diff --git a/packages/cdg-controls/package.json b/packages/cdg-controls/package.json index 102f3cc..90d7b5c 100644 --- a/packages/cdg-controls/package.json +++ b/packages/cdg-controls/package.json @@ -17,7 +17,7 @@ "type": "git", "url": "https://github.com/cutterbl/CDGPlayer.git" }, - "homepage": "https://cutterbl.github.io/CDGPlayer", + "homepage": "https://cutterscrossing.com", "bugs": { "url": "https://github.com/cutterbl/CDGPlayer/issues" }, diff --git a/packages/cdg-core/README.md b/packages/cdg-core/README.md index ba88928..8adfb7a 100644 --- a/packages/cdg-core/README.md +++ b/packages/cdg-core/README.md @@ -60,5 +60,5 @@ const instructions = parser.parseInstructions({ bytes: cdgBytes }); ## Docs -- Architecture: https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-architecture--docs -- Player contract: https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-api-player-contract--docs +- Architecture: https://cutterscrossing.com/?path=/docs/documentation-architecture--docs +- Player contract: https://cutterscrossing.com/?path=/docs/documentation-api-player-contract--docs diff --git a/packages/cdg-core/package.json b/packages/cdg-core/package.json index 49e6fe4..40702cf 100644 --- a/packages/cdg-core/package.json +++ b/packages/cdg-core/package.json @@ -17,7 +17,7 @@ "type": "git", "url": "https://github.com/cutterbl/CDGPlayer.git" }, - "homepage": "https://cutterbl.github.io/CDGPlayer", + "homepage": "https://cutterscrossing.com", "bugs": { "url": "https://github.com/cutterbl/CDGPlayer/issues" }, diff --git a/packages/cdg-loader/README.md b/packages/cdg-loader/README.md index c84aa2e..45ce7bc 100644 --- a/packages/cdg-loader/README.md +++ b/packages/cdg-loader/README.md @@ -63,5 +63,5 @@ You can use worker-backed loading via `loadInWorker(...)` with automatic fallbac ## Docs -- Loader contract: https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-api-loader-contract--docs -- Migration guide: https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-migration-guide--docs +- Loader contract: https://cutterscrossing.com/?path=/docs/documentation-api-loader-contract--docs +- Migration guide: https://cutterscrossing.com/?path=/docs/documentation-migration-guide--docs diff --git a/packages/cdg-loader/package.json b/packages/cdg-loader/package.json index 72241cd..2226d1a 100644 --- a/packages/cdg-loader/package.json +++ b/packages/cdg-loader/package.json @@ -17,7 +17,7 @@ "type": "git", "url": "https://github.com/cutterbl/CDGPlayer.git" }, - "homepage": "https://cutterbl.github.io/CDGPlayer", + "homepage": "https://cutterscrossing.com", "bugs": { "url": "https://github.com/cutterbl/CDGPlayer/issues" }, diff --git a/packages/cdg-player/README.md b/packages/cdg-player/README.md index 3b9236d..7810d44 100644 --- a/packages/cdg-player/README.md +++ b/packages/cdg-player/README.md @@ -67,5 +67,5 @@ player.setPitchSemitones({ value: -2 }); ## Docs -- Player contract: https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-api-player-contract--docs -- Controls contract: https://cutterbl.github.io/CDGPlayer/?path=/docs/documentation-api-controls-contract--docs +- Player contract: https://cutterscrossing.com/?path=/docs/documentation-api-player-contract--docs +- Controls contract: https://cutterscrossing.com/?path=/docs/documentation-api-controls-contract--docs diff --git a/packages/cdg-player/package.json b/packages/cdg-player/package.json index 8df6771..8d3ef9d 100644 --- a/packages/cdg-player/package.json +++ b/packages/cdg-player/package.json @@ -17,7 +17,7 @@ "type": "git", "url": "https://github.com/cutterbl/CDGPlayer.git" }, - "homepage": "https://cutterbl.github.io/CDGPlayer", + "homepage": "https://cutterscrossing.com", "bugs": { "url": "https://github.com/cutterbl/CDGPlayer/issues" }, diff --git a/packages/logger/package.json b/packages/logger/package.json index 8b2b4b2..b850279 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -16,7 +16,7 @@ "type": "git", "url": "https://github.com/cutterbl/CDGPlayer.git" }, - "homepage": "https://cutterbl.github.io/CDGPlayer", + "homepage": "https://cutterscrossing.com", "bugs": { "url": "https://github.com/cutterbl/CDGPlayer/issues" },