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>
34 lines
1.7 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|