Skip to content

fix: stem mixer on iOS — install play/pause shims via defineProperty - #17

Merged
byrongamatos merged 2 commits into
mainfrom
fix/ios-play-pause-readonly-shim
Jul 1, 2026
Merged

byrongamatos merged 2 commits into
mainfrom
fix/ios-play-pause-readonly-shim

Conversation

@byrongamatos

Copy link
Copy Markdown
Collaborator

Closes #16.

Problem

On iOS (iPhone/iPad, run through the native feedback-client-app WKWebView) a multi-stem sloppak plays only one stem and the mixer sliders do nothing. Desktop/Electron unaffected.

installAudioShims() installed the #audio play/pause shims by direct assignment (core.play = fn). iOS WebKit makes those methods non-writable, so the assignment throws "Attempted to assign to readonly property" in the plugin's strict-mode ES-module context. playOk/pauseOk stay false → shimsUsable false → onSongReady() aborts the takeover and hands playback to the native <audio>, which only plays stems[0]. Proxy logs from the iOS client confirmed only stems/guitar.ogg (= stems[0]) was ever fetched.

Fix

Install play/pause with Object.defineProperty(core, …, { configurable: true, writable: true, value: fn }) — an own property on the instance, which succeeds even when the inherited method is non-writable, on both WebKit and Chromium. This matches the existing currentTime/paused/duration shims (already defineProperty, already working).

Tests

tests/ios-play-pause-shim.test.mjs:

  • guards that screen.js no longer uses the fragile core.play = function / core.pause = function assignment and uses defineProperty for both;
  • behaviorally proves defineProperty overrides a non-writable method on an iOS-style element where a plain assignment throws.

node --test → all green (9/9).

Notes

  • Server-served plugin, so once merged and bundled it reaches the iOS client with no app rebuild.
  • CHANGELOG + plugin.json bumped to 0.7.1.
  • Not yet verified on a physical device end-to-end (org CI is billing-blocked); the fix is verified by unit test and by the root-cause proof above. Worth a quick on-device confirm after bundling.

iOS WebKit exposes HTMLMediaElement.play/.pause as non-writable, so the
`core.play = fn` / `core.pause = fn` assignments in installAudioShims()
threw "Attempted to assign to readonly property" in the plugin's strict-mode
module context. That left playOk/pauseOk false → shimsUsable false, so
onSongReady() refused the sloppak takeover and the browser played only
stems[0] (single stem, dead mixer sliders). Chromium/Electron allowed the
assignment, so only iOS was affected.

Install play/pause with Object.defineProperty (own property on the instance),
which works even when the inherited method is non-writable — matching the
currentTime/paused/duration shims. Adds a regression test and bumps to 0.7.1.

Closes #16

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 1, 2026 10:05
@byrongamatos

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes iOS WKWebView stem-mixer takeover failures by installing #audio play/pause shims via Object.defineProperty, avoiding strict-mode assignment errors on non-writable WebKit media methods.

Changes:

  • Update installAudioShims() to define core.play / core.pause via Object.defineProperty (consistent with other shims).
  • Add a regression/unit test covering the iOS non-writable method override behavior and guarding against reintroducing the fragile assignment pattern.
  • Bump plugin version to 0.7.1 and document the fix in the changelog.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
tests/ios-play-pause-shim.test.mjs Adds regression coverage ensuring play/pause shims use defineProperty and validates behavior on a non-writable-method model.
screen.js Switches play/pause shim installation from direct assignment to Object.defineProperty to work on iOS WebKit.
plugin.json Bumps plugin version to 0.7.1.
CHANGELOG.md Documents the iOS stem playback fix for 0.7.1.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/ios-play-pause-shim.test.mjs
Copilot review on PR #17 flagged the assignment-pattern guard as too
narrow: it only matched `core.play = function` / `core.pause = function`
(literal "function" keyword, single-quoted defineProperty target), so a
regression using an arrow function or a differently-quoted defineProperty
call would slip through undetected while still throwing on iOS WebKit.

Broaden the guard to reject any `core.play =` / `core.pause =` assignment
(after stripping full-line comments, since the neighboring comment in
screen.js literally contains the string "core.play = fn" to explain the
fix), and accept either quote style in the defineProperty assertions.

Verified: manually mutated screen.js to reintroduce the bug via an arrow
function assignment and confirmed the tightened guard now fails as
expected; restored and re-ran full suite (9/9 green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos
byrongamatos requested a review from Copilot July 1, 2026 10:12
@byrongamatos

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@byrongamatos
byrongamatos merged commit b590afc into main Jul 1, 2026
1 check passed
@byrongamatos
byrongamatos deleted the fix/ios-play-pause-readonly-shim branch July 1, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stem mixer plays only one stem on iOS — play/pause shim install throws on readonly property

2 participants