Files

103 lines
6.1 KiB
Markdown
Raw Permalink Normal View History

# Share a video to QueueCube
Open QueueCube once after updating to make saved players available to the share
extension. In Safari or a video app, share one HTTP(S) link, choose QueueCube,
and tap a saved player. The link is appended
and played immediately; existing queue entries remain. A successful request
shows the selected player and a Done button. Video support depends on the
player's existing mpv/yt-dlp support.
The extension shows only saved players and does not browse for nearby players.
Add players manually or with Bonjour discovery in QueueCube Settings first.
Allow local network access in the containing app to reach local players.
The extension handles URL attachments
(including UTF-8 data and the absolute-URL property-list form observed from
UIKit) and plain text containing one link. URL data keeps the same single-link
validation; property lists with a nonempty base or properties dictionary and
unrecognized binary forms are rejected.
Multiple different links, files, and links
with embedded user/password information are rejected.
## Data and permissions
The app and `net.buzzert.QueueCube.Share` extension share
`group.net.buzzert.QueueCube`. The app retains its original UserDefaults and
publishes a single versioned settings snapshot on read/save. The extension reads
that snapshot without changing the app's selected player or server list. No
queued link, credentials store, background transfer, or keychain group is added.
The extension has a local network usage declaration and the same HTTP transport
compatibility as the existing app; Catalyst adds only app sandbox and outgoing
network access.
The repository has no application authentication, login flow, or token store.
Requests reuse the existing URLSession client and configured server URLs.
Browser login cookies and credentials cached in another process are not shared.
Servers behind an authenticated reverse proxy may fail; this change does not
introduce a new authentication protocol or bypass access controls.
## Existing API sequence and failure behavior
All paths below are relative to the configured API base URL (usually `/api`).
No backend deployment is required.
| Request | Purpose |
| --- | --- |
| `GET /playlist` | Snapshot existing mpv item IDs before append. |
| `POST /playlist` with `{"url":"https://…"}` | Append once using the existing append-play operation. Starts only if idle. |
| `GET /playlist` | Poll for exactly one new ID matching the URL, then obtain its current zero-based index. |
| `POST /skip/{index}` | Select the new entry while retaining the queue. |
| `POST /play` | Clear pause, including on an already paused player. |
| `GET /nowplaying` | Confirm the new ID is current, is not paused, has no reported playback error, and its playback position advances across reads. |
The current server may acknowledge mutations before mpv finishes and may return
HTTP success even for an mpv failure. The extension verifies reads instead of
assuming a 2xx response means playback started. A selected but still loading
item does not count as success; playback position must advance for the intended
item. Verification polling checks a shared 20-second deadline between operations; each
HTTP request has an 8-second timeout. In-flight or sequential mutation requests
can extend completion beyond that window. Slow media may remain queued without
confirmed playback. This is a point-in-time
confirmation; media loading or decoding can fail later.
If preflight fails, no link has been added and the user can retry. Once append
has been attempted, any failure is treated as potentially partially completed:
the UI says either the link may already be queued or it was queued but playback
was not confirmed. It disables repeat submission for that share session and
asks the user to check QueueCube before sharing again. It never clears the queue,
rolls back another client's changes, or automatically retries a mutation.
Identical URLs already in the queue are distinguished using mpv IDs. Concurrent
identical new entries are ambiguous and are not selected. The existing skip API
uses an index, so another client can reorder the queue between lookup and skip;
verification catches a different resulting item, but cannot make these existing
endpoints atomic. No new backend contract is assumed.
## Building and validation
Build the QueueCube scheme to embed the extension on iOS and Mac Catalyst:
```sh
xcodebuild -project ios/QueueCube.xcodeproj -scheme QueueCube -configuration Debug -destination 'generic/platform=iOS Simulator' -derivedDataPath /tmp/queuecube-build-simulator CODE_SIGNING_ALLOWED=NO build
xcodebuild -project ios/QueueCube.xcodeproj -scheme QueueCube -configuration Debug -destination 'platform=macOS,variant=Mac Catalyst' -derivedDataPath /tmp/queuecube-build-catalyst CODE_SIGNING_ALLOWED=NO build
swift test --package-path ios --scratch-path /tmp/queuecube-share-tests
```
Unsigned builds validate compilation and embedding; they do not validate App
Group provisioning. For simulator UI tests of saved players, use normal local
ad-hoc signing (`CODE_SIGNING_ALLOWED=YES CODE_SIGN_IDENTITY=- DEVELOPMENT_TEAM=`)
so Simulator registers the App Group container. This does not use an Apple
signing identity or provision a physical device. Before a future signed distribution,
register the extension ID and associate **both** app IDs with the App Group,
then update their profiles through the existing signing workflow. Fastlane maps
`QueueCube AppStore CI` and `QueueCube Share AppStore CI` separately and keeps
app/extension versions synchronized. Do not apply a single global bundle ID or
provisioning profile override to both targets.
For manual UI validation, use a dedicated simulator or test installation:
configure a test player in QueueCube, share a URL from Safari, verify saved-player
tap-to-play, the empty saved-player state, cancellation, invalid text, and
an offline/partially failing player. Confirm the old queue is retained and the
new item is current and unpaused. On macOS also confirm QueueCube appears in
Share extensions and can be invoked from a host app. Record signed-device and
live-player gaps explicitly; unit mocks cannot establish those behaviors.