ios: add share extension to queue and play video links
Embed an iOS and Catalyst share extension using existing discovery and player APIs. Share saved players through an App Group snapshot, preserve the queue, and verify advancing playback before reporting success. Cover URL provider transport, settings migration, discovery addresses, duplicate handling, cancellation, and partial enqueue/play outcomes with focused tests. Document authentication and signing limits.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# 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,
|
||||
select a saved or nearby player, and tap **Queue & Play**. 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.
|
||||
|
||||
Nearby players use the same `_queuecube._tcp.` Bonjour discovery as Add Server.
|
||||
Resolved IPv4 interface suffixes are removed; scoped IPv6 addresses remain valid.
|
||||
Allow local network access in the containing app first. Players can also be
|
||||
configured manually in QueueCube Settings. 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 Bonjour/local network declarations 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 and
|
||||
nearby player selection, Queue & Play success, 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.
|
||||
Reference in New Issue
Block a user