ios怎么用spotify
Authenticating through an API like Spotify in order to access and modify a user’s information, music, follows, and so on can be so confusing especially for beginners. Why you may ask? If you have little to no knowledge about dealing with headers, SDK, web APIs, tweaking Xcode’s Build Settings and Info.plist then you are in the right place. My goal in this article is to improve Spotify’s Authorization Guide with better visuals, clearer step by step, actual Swift codes, and more importantly, have my readers level up.
通过诸如Spotify之类的API进行身份验证以访问和修改用户的信息,音乐,跟随等内容可能会造成混乱,尤其是对于初学者而言。 你为什么会问? 如果您几乎不了解处理标头,SDK,Web API,调整Xcode的Build Settings和Info.plist,那么您来对地方了。 本文的目的是通过更好的视觉效果,更清晰的逐步操作,实际的Swift代码,以及更重要的是提高读者的水平来改进Spotify的授权指南。
我们去取得它 (Let’s get it)
I will go over refreshable authorization and authentication process in Swift using Spotify’s iOS SDK. This is a task all software engineers should be comfortable with especially for a very secure backend like Spotify.
我将使用Spotify的iOS SDK讨论Swift中可刷新的授权和身份验证过程。 这是所有软件工程师都应该适应的任务,尤其是对于像Spotify这样的非常安全的后端。
准备环境 (Prepare Your Environment)
I will assume that you registered your app and have a client Identifier from Spotify’s Developer Dashboard. If not, read through Spotify’s iOS SDK Quick Start and App Settings guide. It is important to have appName://
whitelisted as a redirect URIs in your project in Spotify Dashboard in order for Spotify to know how to go back to your app.
我将假设您已经注册了应用程序,并且具有Spotify开发人员仪表板中的客户端标识符。 如果没有,请仔细阅读Spotify的iOS SDK快速入门和应用设置指南。 重要的是,在Spotify信息中心的项目中将appName://
作为重定向URI列入白名单,以便Spotify知道如何返回到您的应用程序。
在Xcode中设置Spotify的iOS SDK (Setup Spotify’s iOS SDK in Xcode)
Skip this section if you are not planning to use Spotify’s iOS SDK. If you want to make your user’s experience as delightful as possible, download the iOS SDK from GitHub and add it to your Xcode project.
如果您不打算使用Spotify的iOS SDK,请跳过本节。 如果您想使用户的体验尽可能愉悦,请从GitHub下载iOS SDK ,并将其添加到您的Xcode项目中。
Create a new file, header file, and name it like so AppName-Bridging-Header
. Then replace all its contents with#import <SpotifyiOS/SpotifyiOS.h>
. Your project navigator should look like the image below. Do not worry about the GoogleService-Info.plist file unless you have pods installed.
创建一个新文件,头文件,并命名为AppName-Bridging-Header
。 然后将其所有内容替换为#import <SpotifyiOS/SpotifyiOS.h>
。 您的项目导航器应如下图所示。 除非您已安装Pod,否则不必担心GoogleService-Info.plist文件。
设置-ObjC链接器标志 (Set -ObjC Linker Flag)
In order to support the iOS SDK and compile the Objective-C code contained inside the iOS SDK, we will need to add -Objc
linker flag. In Xcode, add the linker flag like the image below.
为了支持iOS SDK并编译iOS SDK中包含的Objective-C代码,我们需要添加-Objc
链接器标志。 在Xcode中,添加链接器标志,如下图所示。
添加桥接标题 (Add Bridging Header)
Then we need to add a bridging header next that will allow us to include Objective-C binaries inside our Swift app. We can do that by searching Objective-C Bridging Header in Build Settings and settings its value the same as the name of our header file like the image below
然后,我们需要添加一个桥接标头,该标头将使我们能够在Swift应用程序中包含Objective-C二进制文件。 我们可以通过在“构建设置”中搜索“ Objective-C桥接头”并将其值设置为与我们的头文件的名称相同的方式来做到这一点,如下图所示
确认信息(Confirm Info)
You will also need to add a new URL Types in Info tab. Give the identifier as your Bundle Id, and the value as your callback without the ://
like the image below.
您还需要在“信息”选项卡中添加新的URL类型。 给出标识符作为您的Bundle ID,并给值作为不带://
回调, ://
图所示。
Lastly for security purposes, you will need to open Info.plist as source code and make sure you tell NSAppTransportSecurity that you are supporting the domain, spotify.com. Take this time to also make sure that you have the same changes on your Info.plist as mine that are marked with blue horizontal lines.
最后,出于安全性考虑,您需要打开Info.plist作为源代码,并确保告知NSAppTransportSecurity您正在支持域Spotify.com。 请花一些时间确保在Info.plist上所做的更改与用蓝色水平线标记的更改相同。
授权流程 (Authorization Flows)
Spotify comes with four flows to obtain app authorization. Those are:
Spotify附带四个流程来获取应用程序授权。 那些是:
Refreshable user authorization: Authorization Code Flow
可刷新的用户授权:授权码流程
Refreshable user authorization: Authorization Code Flow With Proof Key for Code Exchange (PKCE)
可刷新的用户授权:具有用于代码交换(PKCE)的证明密钥的授权代码流
Temporary user authorization: Implicit Grant
临时用户授权:隐式授予
Refreshable app authorization: Client Credentials Flow
可刷新的应用授权:客户端凭据流
In this article, we will be following the second option, Authorization Code Flow With Proof Key for Code Exchange (PKCE). According to Spotify, authorization code flow with PKCE is the best option for mobile and desktop applications because it is unsafe to store client secret. It also provides your app with an access token that can be refreshed.
在本文中,我们将采用第二种选择,即带有代码交换证明密钥的授权代码流(PKCE) 。 根据Spotify的说法,带有PKCE的授权码流是移动和桌面应用程序的最佳选择,因为它不安全地存储客户端机密。 它还为您的应用程序提供了可以刷新的访问令牌。
编码时间 (Time to Code)
It’s time to finally code. My code mostly came from one of Spotify’s iOS SDK Demo Projects, SPTLoginSampleAppSwift.
现在是时候开始编写代码了。 我的代码主要来自Spotify的iOS SDK演示项目之一SPTLoginSampleAppSwift 。
From Authorization Code Flow With Proof Key for Code Exchange (PKCE) it is telling us to 1. Create the code verifier challenge then 2. Construct the authorization URI. Fortunately for us, since we are using the Spotify iOS SDK, we can complete those two steps by initiating a session with our session manager. Simply call the following method on button tap.
从带有代码交换证明密钥(PKCE)的授权代码流中,我们告诉我们1.创建代码验证者质询,然后2.构造授权URI 。 对我们来说幸运的是,由于我们使用的是Spotify iOS SDK,因此可以通过与会话管理器启动会话来完成这两个步骤。 只需在按钮点击上调用以下方法。
@objc func didTapConnect(_ button: UIButton) {
guard let sessionManager = sessionManager else { return }
if #available(iOS 11, *) {
// Use this on iOS 11 and above to take advantage of SFAuthenticationSession
sessionManager.initiateSession(with: scopes, options: .clientOnly)
} else {
// Use this on iOS versions < 11 to use SFSafariViewController
sessionManager.initiateSession(with: scopes, options: .clientOnly, presenting: self)
}
}
The next step is 3. Your app redirects the user to the authorization URI. After initiating a session with session manager, our Spotify app will be launched to get permissions specified in scopes. If user accepts, we will get the code
we need to get our access token.
下一步是3。您的应用将用户重定向到授权URI 。 与会话管理器启动会话后,将启动我们的Spotify应用以获取在范围中指定的权限。 如果用户接受,我们将获得获取访问令牌所需的code
。
The last thing we need to do is 4. Exchange the authorization code for an access token. To do that we will need to make a POST request to https://accounts.spotify.com/api/token
endpoint with the following body (client_id, grant_type, code, redirect_uri) filled out.
我们需要做的最后一件事是4.交换访问令牌的授权代码。 为此,我们需要向https://accounts.spotify.com/api/token
端点发出POST请求,并填写以下正文(client_id,grant_type,代码,redirect_uri)。
///fetch Spotify access token. Use after getting responseTypeCode
func fetchSpotifyToken(completion: @escaping ([String: Any]?, Error?) -> Void) {
let url = URL(string: "https://accounts.spotify.com/api/token")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let spotifyAuthKey = "Basic \((spotifyClientId + ":" + spotifyClientSecretKey).data(using: .utf8)!.base64EncodedString())"
request.allHTTPHeaderFields = ["Authorization": spotifyAuthKey, "Content-Type": "application/x-www-form-urlencoded"]
do {
var requestBodyComponents = URLComponents()
let scopeAsString = stringScopes.joined(separator: " ") //put array to string separated by whitespace
requestBodyComponents.queryItems = [URLQueryItem(name: "client_id", value: spotifyClientId), URLQueryItem(name: "grant_type", value: "authorization_code"), URLQueryItem(name: "code", value: responseTypeCode!), URLQueryItem(name: "redirect_uri", value: redirectUri.absoluteString), URLQueryItem(name: "code_verifier", value: codeVerifier), URLQueryItem(name: "scope", value: scopeAsString),]
request.httpBody = requestBodyComponents.query?.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, // is there data
let response = response as? HTTPURLResponse, // is there HTTP response
(200 ..< 300) ~= response.statusCode, // is statusCode 2XX
error == nil else { // was there no error, otherwise ...
print("Error fetching token \(error?.localizedDescription ?? "")")
return completion(nil, error)
}
let responseObject = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
print("Access Token Dictionary=", responseObject ?? "")
completion(responseObject, nil)
}
task.resume()
} catch {
print("Error JSON serialization \(error.localizedDescription)")
}
}
According to the guide, the exchange requires code_verifier
to be included in the body, however, at the time of this writing, it is not required since we are using the iOS SDK. It may be required for web API authorization and authentication flow.
根据指南,交换要求将code_verifier
包含在主体中,但是在撰写本文时,由于我们使用的是iOS SDK,因此不需要。 Web API授权和身份验证流程可能需要它。
而已! (That’s it!)
Congratulations on successfully doing an authorization and authenticating using Spotify iOS SDK. You have leveled up in iOS development 👏🏼👏🏼👏🏼👏🏼👏🏼
祝贺您使用Spotify iOS SDK成功完成了授权和身份验证。 您已升级iOS开发development
挑战 (Challenge)
You may still be a little lost on what is going on, but that’s okay. Repetition is key to grasp these concepts. I highly suggest you do the challenge I have below.
您可能对所发生的事情仍然有些迷茫,但这没关系。 重复是掌握这些概念的关键。 我强烈建议您做下面的挑战。
Complete this by finishing the flow and access user’s information with the access token and requesting a refreshed access token.
通过完成流程并使用访问令牌访问用户信息并请求刷新的访问令牌来完成此操作。
Try authorization and authentication using Facebook’s iOS SDK
使用Facebook的iOS SDK尝试授权和身份验证
完整的源代码(Full Source Code)
For the full source code, click here.
有关完整的源代码,请单击此处。
//Constants.swiftimport Foundationlet accessTokenKey = "access-token-key"
let redirectUri = URL(string:"previewtify://")!
let spotifyClientId = "e9d953c9eff4433cb30acf3e4866a68d"
let spotifyClientSecretKey = "e891fd17090d4841afaf88c5730419a9"/*
Scopes let you specify exactly what types of data your application wants to access, and the set of scopes you pass in your call determines what access permissions the user is asked to grant. For more information, see https://developer.spotify.com/web-api/using-scopes/.
*///remove scopes you don't need
let scopes: SPTScope = [.userReadEmail, .userReadPrivate,
.userReadPlaybackState, .userModifyPlaybackState,
.userReadCurrentlyPlaying, .streaming, .appRemoteControl,
.playlistReadCollaborative, .playlistModifyPublic, .playlistReadPrivate, .playlistModifyPrivate,
.userLibraryModify, .userLibraryRead,
.userTopRead, .userReadPlaybackState, .userReadCurrentlyPlaying,
.userFollowRead, .userFollowModify,]//remove scopes you don't need
let stringScopes = ["user-read-email", "user-read-private",
"user-read-playback-state", "user-modify-playback-state", "user-read-currently-playing",
"streaming", "app-remote-control",
"playlist-read-collaborative", "playlist-modify-public", "playlist-read-private", "playlist-modify-private",
"user-library-modify", "user-library-read",
"user-top-read", "user-read-playback-position", "user-read-recently-played",
"user-follow-read", "user-follow-modify",]
SceneDelegate.swift
SceneDelegate.swift
import UIKitclass SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
lazy var rootViewController = ViewController() func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: UIScreen.main.bounds)
window!.makeKeyAndVisible()
window!.windowScene = windowScene
window!.rootViewController = rootViewController
}//for spotify authorization and authentication flow
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
let parameters = rootViewController.appRemote.authorizationParameters(from: url)
if let code = parameters?["code"] {
rootViewController.responseTypeCode = code
} else if let access_token = parameters?[SPTAppRemoteAccessTokenKey] {
rootViewController.accessToken = access_token
} else if let error_description = parameters?[SPTAppRemoteErrorDescriptionKey] {
print("No access token error =", error_description)
}
} func sceneDidBecomeActive(_ scene: UIScene) {
if let accessToken = rootViewController.appRemote.connectionParameters.accessToken {
rootViewController.appRemote.connectionParameters.accessToken = accessToken
rootViewController.appRemote.connect()
} else if let accessToken = rootViewController.accessToken {
rootViewController.appRemote.connectionParameters.accessToken = accessToken
rootViewController.appRemote.connect()
}
} func sceneWillResignActive(_ scene: UIScene) {
if rootViewController.appRemote.isConnected {
rootViewController.appRemote.disconnect()
}
}
}
ViewController.swift
ViewController.swift
import UIKitclass ViewController: UIViewController {
var responseTypeCode: String? {
didSet {
fetchSpotifyToken { (dictionary, error) in
if let error = error {
print("Fetching token request error \(error)")
return}
let accessToken = dictionary!["access_token"] as! String
DispatchQueue.main.async {
self.appRemote.connectionParameters.accessToken = accessToken
self.appRemote.connect()
}
}
}
} lazy var appRemote: SPTAppRemote = {
let appRemote = SPTAppRemote(configuration: configuration, logLevel: .debug)
appRemote.connectionParameters.accessToken = self.accessToken
appRemote.delegate = self
return appRemote
}() var accessToken = UserDefaults.standard.string(forKey: accessTokenKey) {
didSet {
let defaults = UserDefaults.standard
defaults.set(accessToken, forKey: accessTokenKey)
}
} lazy var configuration: SPTConfiguration = {
let configuration = SPTConfiguration(clientID: spotifyClientId, redirectURL: redirectUri)
// Set the playURI to a non-nil value so that Spotify plays music after authenticating and App Remote can connect
// otherwise another app switch will be required
configuration.playURI = ""
// Set these url's to your backend which contains the secret to exchange for an access token
// You can use the provided ruby script spotify_token_swap.rb for testing purposes
configuration.tokenSwapURL = URL(string: "http://localhost:1234/swap")
configuration.tokenRefreshURL = URL(string: "http://localhost:1234/refresh")
return configuration
}() lazy var sessionManager: SPTSessionManager? = {
let manager = SPTSessionManager(configuration: configuration, delegate: self)
return manager
}() private var lastPlayerState: SPTAppRemotePlayerState? // MARK: - Subviews private lazy var connectLabel: UILabel = {
let label = UILabel()
label.text = "Connect your Spotify account"
label.font = UIFont.systemFont(ofSize: 20, weight: .bold)
label.textColor = UIColor(red:(29.0 / 255.0), green:(185.0 / 255.0), blue:(84.0 / 255.0), alpha:1.0)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}() private lazy var connectButton: UIButton = {
let button = UIButton()
button.backgroundColor = UIColor(red:(29.0 / 255.0), green:(185.0 / 255.0), blue:(84.0 / 255.0), alpha:1.0)
button.translatesAutoresizingMaskIntoConstraints = falsebutton.contentEdgeInsets = UIEdgeInsets(top: 11.75, left: 32.0, bottom: 11.75, right: 32.0)
button.layer.cornerRadius = 20.0
button.setTitle("Continue with Spotify", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 20, weight: .bold)
button.sizeToFit()
button.addTarget(self, action: #selector(didTapConnect(_:)), for: .touchUpInside)
return button
}() private lazy var disconnectButton: UIButton = {
let button = UIButton()
button.backgroundColor = UIColor(red:(29.0 / 255.0), green:(185.0 / 255.0), blue:(84.0 / 255.0), alpha:1.0)
button.translatesAutoresizingMaskIntoConstraints = falsebutton.contentEdgeInsets = UIEdgeInsets(top: 11.75, left: 32.0, bottom: 11.75, right: 32.0)
button.layer.cornerRadius = 20.0
button.setTitle("Sign out", for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 20, weight: .bold)
button.sizeToFit()
button.addTarget(self, action: #selector(didTapDisconnect(_:)), for: .touchUpInside)
return button
}() private lazy var pauseAndPlayButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(didTapPauseOrPlay), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = falsebutton.sizeToFit()
return button
}() private lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = falseimageView.contentMode = .scaleAspectFit
return imageView
}() private lazy var trackLabel: UILabel = {
let trackLabel = UILabel()
trackLabel.translatesAutoresizingMaskIntoConstraints = falsetrackLabel.textColor = .black
trackLabel.font = UIFont.systemFont(ofSize: 18, weight: .medium)
trackLabel.textAlignment = .center
return trackLabel
}() //MARK: App Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
} override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateViewBasedOnConnected()
} //MARK: Methods
func setupViews() {
view.backgroundColor = UIColor.white
view.addSubview(connectLabel)
view.addSubview(connectButton)
view.addSubview(disconnectButton)
view.addSubview(imageView)
view.addSubview(trackLabel)
view.addSubview(pauseAndPlayButton)
let constant: CGFloat = 16.0
connectButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = trueconnectButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = truedisconnectButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = truedisconnectButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = trueconnectLabel.centerXAnchor.constraint(equalTo: connectButton.centerXAnchor).isActive = trueconnectLabel.bottomAnchor.constraint(equalTo: connectButton.topAnchor, constant: -constant).isActive = trueimageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = trueimageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64).isActive = trueimageView.bottomAnchor.constraint(equalTo: trackLabel.topAnchor, constant: -constant).isActive = truetrackLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = truetrackLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: constant).isActive = truetrackLabel.bottomAnchor.constraint(equalTo: connectLabel.topAnchor, constant: -constant).isActive = truepauseAndPlayButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = truepauseAndPlayButton.topAnchor.constraint(equalTo: trackLabel.bottomAnchor, constant: constant).isActive = truepauseAndPlayButton.widthAnchor.constraint(equalToConstant: 50).isActive = truepauseAndPlayButton.heightAnchor.constraint(equalToConstant: 50).isActive = trueupdateViewBasedOnConnected()
} func update(playerState: SPTAppRemotePlayerState) {
if lastPlayerState?.track.uri != playerState.track.uri {
fetchArtwork(for: playerState.track)
}
lastPlayerState = playerState
trackLabel.text = playerState.track.name
if playerState.isPaused {
pauseAndPlayButton.setImage(UIImage(named: "play"), for: .normal)
} else {
pauseAndPlayButton.setImage(UIImage(named: "pause"), for: .normal)
}
} func updateViewBasedOnConnected() {
if appRemote.isConnected == true {
connectButton.isHidden = truedisconnectButton.isHidden = falseconnectLabel.isHidden = trueimageView.isHidden = falsetrackLabel.isHidden = falsepauseAndPlayButton.isHidden = false} else { //show login
disconnectButton.isHidden = trueconnectButton.isHidden = falseconnectLabel.isHidden = falseimageView.isHidden = truetrackLabel.isHidden = truepauseAndPlayButton.isHidden = true}
} func fetchArtwork(for track: SPTAppRemoteTrack) {
appRemote.imageAPI?.fetchImage(forItem: track, with: CGSize.zero, callback: { [weak self] (image, error) in
if let error = error {
print("Error fetching track image: " + error.localizedDescription)
} else if let image = image as? UIImage {
self?.imageView.image = image
}
})
} func fetchPlayerState() {
appRemote.playerAPI?.getPlayerState({ [weak self] (playerState, error) in
if let error = error {
print("Error getting player state:" + error.localizedDescription)
} else if let playerState = playerState as? SPTAppRemotePlayerState {
self?.update(playerState: playerState)
}
})
} // MARK: - Actions @objc func didTapPauseOrPlay(_ button: UIButton) {
if let lastPlayerState = lastPlayerState, lastPlayerState.isPaused {
appRemote.playerAPI?.resume(nil)
} else {
appRemote.playerAPI?.pause(nil)
}
} @objc func didTapDisconnect(_ button: UIButton) {
if appRemote.isConnected == true {
appRemote.disconnect()
}
} @objc func didTapConnect(_ button: UIButton) {
guard let sessionManager = sessionManager else { return }
if #available(iOS 11, *) {
// Use this on iOS 11 and above to take advantage of SFAuthenticationSession
sessionManager.initiateSession(with: scopes, options: .clientOnly)
} else {
// Use this on iOS versions < 11 to use SFSafariViewController
sessionManager.initiateSession(with: scopes, options: .clientOnly, presenting: self)
}
}// MARK: - Private Helpers private func presentAlertController(title: String, message: String, buttonTitle: String) {
DispatchQueue.main.async {
let controller = UIAlertController(title: title, message: message, preferredStyle: .alert)
let action = UIAlertAction(title: buttonTitle, style: .default, handler: nil)
controller.addAction(action)
self.present(controller, animated: true)
}
} //MARK: POST Request///fetch Spotify access token. Use after getting responseTypeCode
func fetchSpotifyToken(completion: @escaping ([String: Any]?, Error?) -> Void) {
let url = URL(string: "https://accounts.spotify.com/api/token")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let spotifyAuthKey = "Basic \((spotifyClientId + ":" + spotifyClientSecretKey).data(using: .utf8)!.base64EncodedString())"
request.allHTTPHeaderFields = ["Authorization": spotifyAuthKey, "Content-Type": "application/x-www-form-urlencoded"]
do {
var requestBodyComponents = URLComponents()
let scopeAsString = stringScopes.joined(separator: " ") //put array to string separated by whitespace
requestBodyComponents.queryItems = [URLQueryItem(name: "client_id", value: spotifyClientId), URLQueryItem(name: "grant_type", value: "authorization_code"), URLQueryItem(name: "code", value: responseTypeCode!), URLQueryItem(name: "redirect_uri", value: redirectUri.absoluteString), URLQueryItem(name: "code_verifier", value: codeVerifier), URLQueryItem(name: "scope", value: scopeAsString),]
request.httpBody = requestBodyComponents.query?.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, // is there data
let response = response as? HTTPURLResponse, // is there HTTP response
(200 ..< 300) ~= response.statusCode, // is statusCode 2XX
error == nil else { // was there no error, otherwise ...
print("Error fetching token \(error?.localizedDescription ?? "")")
return completion(nil, error)
}
let responseObject = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
print("Access Token Dictionary=", responseObject ?? "")
completion(responseObject, nil)
}
task.resume()
} catch {
print("Error JSON serialization \(error.localizedDescription)")
}
}
}
// MARK: - SPTAppRemoteDelegate
extension ViewController: SPTAppRemoteDelegate {
func appRemoteDidEstablishConnection(_ appRemote: SPTAppRemote) {
updateViewBasedOnConnected()
appRemote.playerAPI?.delegate = selfappRemote.playerAPI?.subscribe(toPlayerState: { (success, error) in
if let error = error {
print("Error subscribing to player state:" + error.localizedDescription)
}
})
fetchPlayerState()
} func appRemote(_ appRemote: SPTAppRemote, didDisconnectWithError error: Error?) {
updateViewBasedOnConnected()
lastPlayerState = nil} func appRemote(_ appRemote: SPTAppRemote, didFailConnectionAttemptWithError error: Error?) {
updateViewBasedOnConnected()
lastPlayerState = nil}
}
// MARK: - SPTAppRemotePlayerAPIDelegate
extension ViewController: SPTAppRemotePlayerStateDelegate {
func playerStateDidChange(_ playerState: SPTAppRemotePlayerState) {
debugPrint("Spotify Track name: %@", playerState.track.name)
update(playerState: playerState)
}
}// MARK: - SPTSessionManagerDelegate
extension ViewController: SPTSessionManagerDelegate {
func sessionManager(manager: SPTSessionManager, didFailWith error: Error) {
if error.localizedDescription == "The operation couldn’t be completed. (com.spotify.sdk.login error 1.)" {
print("AUTHENTICATE with WEBAPI")
} else {
presentAlertController(title: "Authorization Failed", message: error.localizedDescription, buttonTitle: "Bummer")
}
}
func sessionManager(manager: SPTSessionManager, didRenew session: SPTSession) {
presentAlertController(title: "Session Renewed", message: session.description, buttonTitle: "Sweet")
} func sessionManager(manager: SPTSessionManager, didInitiate session: SPTSession) {
appRemote.connectionParameters.accessToken = session.accessToken
appRemote.connect()
}
}
Thank you so much for reading my article on how to get authorization and authenticate using a Spotify SDK.
非常感谢您阅读我有关如何使用Spotify SDK获得授权和身份验证的文章。
翻译自: https://medium.com/@samuelfolledo/authenticate-with-spotify-in-ios-ae6612ecca91
ios怎么用spotify