Files
stemify/ios/Stemify/App/SiriIntents.swift
T
cdnutterandClaude Opus 5.5 fdf9e970b2 Add iOS app with CarPlay and Siri
SwiftUI port of the Android app under ios/, with CarPlay browse and
Now Playing, Siri play-media intents, and a build-ipa.sh script that
makes an unsigned IPA for sideloading. README gets an iOS section and
the vibecoded disclaimer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:15:47 -07:00

34 lines
1.7 KiB
Swift

import Intents
/// "Hey Siri, play … on Stemify". Handled in the app process, so it can start playback directly.
final class PlayMediaHandler: NSObject, INPlayMediaIntentHandling {
func resolveMediaItems(for intent: INPlayMediaIntent) async -> [INPlayMediaMediaItemResolutionResult] {
let q = intent.mediaSearch?.mediaName ?? intent.mediaSearch?.artistName ?? intent.mediaSearch?.albumName ?? ""
let item = INMediaItem(identifier: "search:\(q)", title: q.isEmpty ? "Downloads" : q, type: .music, artwork: nil)
return INPlayMediaMediaItemResolutionResult.successes(with: [item])
}
func handle(intent: INPlayMediaIntent) async -> INPlayMediaIntentResponse {
let s = intent.mediaSearch
let q = s?.mediaName ?? s?.artistName ?? s?.albumName ?? intent.mediaItems?.first?.title ?? ""
let shuffle = intent.playShuffled == true
let ok = await Self.play(q, shuffle)
return INPlayMediaIntentResponse(code: ok ? .success : .failure, userActivity: nil)
}
@MainActor static func play(_ query: String, _ shuffle: Bool) async -> Bool {
let g = AppGraph.shared
do {
guard let r = try await g.car.resolveSearch(query) else {
g.playback.report(query.isEmpty ? "Nothing downloaded yet" : "Nothing found for “\(query)”")
return false
}
g.playback.playTracks(r.tracks, start: r.start, shuffle: r.shuffle || shuffle)
return true
} catch {
g.playback.report(error is NotSignedIn ? "Sign in to Plex on your phone" : "Couldn't load that: \(error.localizedDescription)")
return false
}
}
}