Issue: Calling `authorizeAndPlayURI` doesn't setup a `playerAPI` or call any of the `SPTAppRemoteDelegate` functions
SDK Version: Latest release Spotify Version: 8.7.64
Example code:
appRemote = SPTAppRemote(configuration: configuration, logLevel: .debug)
appRemote?.connectionParameters.accessToken = accessToken
appRemote?.delegate = self
guard appRemote?.isConnected == false else {
callback(.success(true))
return
}
// (1) This process is a bit convoluted unfortunately, due to way we connect/control
// spotify we first check if app is active, this will fail if the app isn't installed
// or running. Skip to (2.b)
SPTAppRemote.checkIfSpotifyAppIsActive { [weak self] active in
guard let self = self else { return }
// (2.a) If we're already active then store the closure and ask the remote to
// connect (see (3))
guard !active else {
self.setupCallback = callback
self.appRemote?.connect()
return
}
// (2.b) If this method returns false then the spotify app isn't installed!
let installed = self.appRemote?.authorizeAndPlayURI(self.track.uri ?? "") == true
guard installed else {
callback(.failure(SpotifyAudioManagerError.spotifyNotInstalled))
return
}
self.setupCallback = callback
// (2.c) Await delegate callbacks!
}
Delegate methods:
extension SpotifyAudioManager: SPTAppRemoteDelegate {
func appRemoteDidEstablishConnection(_ appRemote: SPTAppRemote) {
// (3.a) If our `connect()` call succeeds, we can progress
setupCallback?(.success(true))
setupCallback = nil
}
func appRemote(_ appRemote: SPTAppRemote, didDisconnectWithError error: Error?) {
}
func appRemote(_ appRemote: SPTAppRemote, didFailConnectionAttemptWithError error: Error?) {
// (3.b) If our `connect()` call fails, we'll have a last-ditched attempt wake the Spotify app
let installed = appRemote.authorizeAndPlayURI(track.uri ?? "")
guard installed else {
setupCallback?(.failure(SpotifyAudioManagerError.spotifyNotInstalled))
return
}
setupCallback?(.success(true))
setupCallback = nil
}
}
If the spotify app is running/playing music everything works great ✅
If the spotify app is force-quit/not running then the spotify app is launched, connects, starts playing, and my app is re-opened, but I don't get told if the process was successful, and playerAPI is never made non-nil on my SPTAppRemote instance.
I have found a workaround for this however, which is to at point 2.c in my comments add a foreground observer and re-call connect() once the app enters the foreground!
// (2.c) The current version of the spotify app doesn't seem to setup `playerAPI` even
// when the app re-launches after kicking out to spotify. To rectify this we listen
// to app foreground notifications and then retry if we need to!
self.foregroundObserver = NotificationCenter.default.addObserver(
forName: UIApplication.willEnterForegroundNotification,
object: nil,
queue: .main,
using: { [weak self] _ in
// (3.c) Re-attempt connection
guard let self = self else { return }
self.setupCallback = callback
self.appRemote?.connect()
// We're done with this now, so we can remove it!
guard let foregroundObserver = self.foregroundObserver else {
return
}
NotificationCenter.default.removeObserver(foregroundObserver)
}
)
This seems to work for now, but I do not trust it in the slightest, and I'm unsure what kind of state I'll end up in if something in the spotify app fails e.t.c (user isn't logged in, doesn't have premium e.t.c.)
Can anyone advise if:
- I am doing anything wrong? I'm missing some API or class that handles the "Spotify app isn't running" case correctly?
- Will my workaround be okay (will probably take a spotify engineer to work this out)
- Is there anything that can be done to fix this in the root SDK?
Thanks everyone! 😄
Thank you for the workaround snippet here, can confirm this allows for a connection in 8.7.90.459 as well. Trying to guess at the situation that would cause this sort of behavior - some sort of time window of availability for the now-backgrounded apps socket being open? Some sort of interplay of background modes and networking on the iOS SDK side?
FWIW I'm seeing this work about half the time in SDK version 1.2.3 and the Spotify app version 8.8.15.609. Here's my basic code:
AppDelegate:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
if (url.absoluteString.contains("spotify/authorized")) {
self.recordingPlayer?.authorizeSpotify(url: url)
return true
}
}
RecordingPlayer:
func authorizeSpotify(url: URL) {
let parameters = self.spotifyRemote?.authorizationParameters(from: url)
if let token = parameters?[SPTAppRemoteAccessTokenKey] {
self.spotifyRemote?.connectionParameters.accessToken = token
self.spotifyRemote?.connect()
let test = self.spotifyRemote?.isConnected
self.spotifyRemote?.playerAPI?.delegate = self
self.spotifyRemote?.playerAPI?.subscribe()
self.spotifyRemote?.delegate = self
} else if let errorDescription = parameters?[SPTAppRemoteErrorDescriptionKey] {
NSLog("Spotify authentication error: " + errorDescription)
}
}
About half the time, the test variable is true and the subsequent lines work; the other half the time, the test variable is false and the delegate functions aren't called.
Since the problem only happens when the Spotify app was not already running, it seems like it's calling back to our apps too soon, rather than getting suspended before our apps can connect to it. Could this be fixed by the Spotify app waiting until it is ready to receive a connection before reopening our apps?