5 changed files with 401 additions and 61 deletions
@ -0,0 +1,311 @@ |
|||||
|
package de.minimalme.spotify_sdk |
||||
|
|
||||
|
import android.app.Activity |
||||
|
import android.content.Context |
||||
|
import android.content.Intent |
||||
|
import android.util.Log |
||||
|
import com.spotify.android.appremote.api.ConnectionParams |
||||
|
import com.spotify.android.appremote.api.Connector |
||||
|
import com.spotify.android.appremote.api.PlayerApi |
||||
|
import com.spotify.android.appremote.api.SpotifyAppRemote |
||||
|
import com.spotify.android.appremote.api.error.AuthenticationFailedException |
||||
|
import com.spotify.android.appremote.api.error.CouldNotFindSpotifyApp |
||||
|
import com.spotify.android.appremote.api.error.NotLoggedInException |
||||
|
import com.spotify.android.appremote.api.error.OfflineModeException |
||||
|
import com.spotify.android.appremote.api.error.SpotifyConnectionTerminatedException |
||||
|
import com.spotify.android.appremote.api.error.SpotifyDisconnectedException |
||||
|
import com.spotify.android.appremote.api.error.SpotifyRemoteServiceException |
||||
|
import com.spotify.android.appremote.api.error.UnsupportedFeatureVersionException |
||||
|
import com.spotify.android.appremote.api.error.UserNotAuthorizedException |
||||
|
import com.spotify.sdk.android.auth.AuthorizationClient |
||||
|
import com.spotify.sdk.android.auth.AuthorizationRequest |
||||
|
import com.spotify.sdk.android.auth.AuthorizationResponse |
||||
|
import de.minimalme.spotify_sdk.subscriptions.CapabilitiesChannel |
||||
|
import de.minimalme.spotify_sdk.subscriptions.ConnectionStatusChannel |
||||
|
import de.minimalme.spotify_sdk.subscriptions.PlayerContextChannel |
||||
|
import de.minimalme.spotify_sdk.subscriptions.PlayerStateChannel |
||||
|
import de.minimalme.spotify_sdk.subscriptions.UserStatusChannel |
||||
|
import io.flutter.embedding.engine.plugins.FlutterPlugin |
||||
|
import io.flutter.embedding.engine.plugins.activity.ActivityAware |
||||
|
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding |
||||
|
import io.flutter.plugin.common.EventChannel |
||||
|
import io.flutter.plugin.common.MethodCall |
||||
|
import io.flutter.plugin.common.MethodChannel |
||||
|
import io.flutter.plugin.common.PluginRegistry |
||||
|
import kotlinx.event.SetEvent |
||||
|
import kotlinx.event.event |
||||
|
class SpotifySdkService(private val context: Context? = null) { |
||||
|
|
||||
|
// application context |
||||
|
private var applicationContext : Context? = null |
||||
|
private var applicationActivity : Activity? = null |
||||
|
// method channel |
||||
|
private lateinit var methodChannel : MethodChannel |
||||
|
private val channelName = "spotify_sdk" |
||||
|
private val loggingTag = "spotify_sdk" |
||||
|
|
||||
|
// event channels |
||||
|
private var playerContextChannel : EventChannel? = null |
||||
|
private var playerStateChannel : EventChannel? = null |
||||
|
private var capabilitiesChannel : EventChannel? = null |
||||
|
private var userStatusChannel : EventChannel? = null |
||||
|
private var connectionStatusChannel : EventChannel? = null |
||||
|
|
||||
|
private val playerContextSubscription = "player_context_subscription" |
||||
|
private val playerStateSubscription = "player_state_subscription" |
||||
|
private val capabilitiesSubscription = "capabilities_subscription" |
||||
|
private val userStatusSubscription = "user_status_subscription" |
||||
|
private val connectionStatusSubscription = "connection_status_subscription" |
||||
|
|
||||
|
//connecting |
||||
|
private val methodConnectToSpotify = "connectToSpotify" |
||||
|
private val methodGetAccessToken = "getAccessToken" |
||||
|
private val methodDisconnectFromSpotify = "disconnectFromSpotify" |
||||
|
|
||||
|
// connectApi |
||||
|
private val methodSwitchToLocalDevice = "switchToLocalDevice" |
||||
|
|
||||
|
//playerApi |
||||
|
private val methodGetCrossfadeState = "getCrossfadeState" |
||||
|
private val methodGetPlayerState = "getPlayerState" |
||||
|
private val methodPlay = "play" |
||||
|
private val methodPause = "pause" |
||||
|
private val methodQueueTrack = "queueTrack" |
||||
|
private val methodResume = "resume" |
||||
|
private val methodSeekToRelativePosition = "seekToRelativePosition" |
||||
|
private val methodSetPodcastPlaybackSpeed = "setPodcastPlaybackSpeed" |
||||
|
private val methodSkipNext = "skipNext" |
||||
|
private val methodSkipPrevious = "skipPrevious" |
||||
|
private val methodSkipToIndex = "skipToIndex" |
||||
|
private val methodSeekTo = "seekTo" |
||||
|
private val methodToggleRepeat = "toggleRepeat" |
||||
|
private val methodToggleShuffle = "toggleShuffle" |
||||
|
private val methodSetShuffle = "setShuffle" |
||||
|
private val methodSetRepeatMode = "setRepeatMode" |
||||
|
|
||||
|
//userApi |
||||
|
private val methodAddToLibrary = "addToLibrary" |
||||
|
private val methodRemoveFromLibrary = "removeFromLibrary" |
||||
|
private val methodGetCapabilities = "getCapabilities" |
||||
|
private val methodGetLibraryState = "getLibraryState" |
||||
|
|
||||
|
//imagesApi |
||||
|
private val methodGetImage = "getImage" |
||||
|
|
||||
|
private val paramClientId = "clientId" |
||||
|
private val paramRedirectUrl = "redirectUrl" |
||||
|
private val paramScope = "scope" |
||||
|
private val paramSpotifyUri = "spotifyUri" |
||||
|
private val paramImageUri = "imageUri" |
||||
|
private val paramImageDimension = "imageDimension" |
||||
|
private val paramPositionedMilliseconds = "positionedMilliseconds" |
||||
|
private val paramRelativeMilliseconds = "relativeMilliseconds" |
||||
|
private val paramPodcastPlaybackSpeed = "podcastPlaybackSpeed" |
||||
|
private val paramTrackIndex = "trackIndex" |
||||
|
private val paramRepeatMode = "repeatMode" |
||||
|
private val paramShuffle = "shuffle" |
||||
|
|
||||
|
private val errorConnecting = "errorConnecting" |
||||
|
private val errorDisconnecting = "errorDisconnecting" |
||||
|
private val errorConnection = "errorConnection" |
||||
|
private val errorAuthenticationToken = "authenticationTokenError" |
||||
|
|
||||
|
private val requestCodeAuthentication = 1337 |
||||
|
|
||||
|
private var spotifyAppRemote: SpotifyAppRemote? = null |
||||
|
private var playerApi: PlayerApi? = null |
||||
|
|
||||
|
//-- Method implementations |
||||
|
private fun connectToSpotify(clientId: String?, redirectUrl: String?, result: MethodChannel.Result) { |
||||
|
|
||||
|
if (clientId.isNullOrBlank() || redirectUrl.isNullOrBlank()) { |
||||
|
result.error(errorConnecting, "client id or redirectUrl are not set or have invalid format", "") |
||||
|
} else { |
||||
|
val connectionParams = ConnectionParams.Builder(clientId) |
||||
|
.setRedirectUri(redirectUrl) |
||||
|
.showAuthView(true) |
||||
|
.build() |
||||
|
SpotifyAppRemote.disconnect(spotifyAppRemote) |
||||
|
var initiallyConnected = false |
||||
|
SpotifyAppRemote.connect(applicationContext, connectionParams, |
||||
|
object : Connector.ConnectionListener { |
||||
|
override fun onConnected(spotifyAppRemoteValue: SpotifyAppRemote) { |
||||
|
spotifyAppRemote = spotifyAppRemoteValue |
||||
|
playerApi = spotifyAppRemote?.playerApi |
||||
|
|
||||
|
initiallyConnected = true |
||||
|
Log.i(loggingTag, "App Remote successfully connected") |
||||
|
result.success(true) |
||||
|
} |
||||
|
|
||||
|
override fun onFailure(throwable: Throwable) { |
||||
|
val errorDetails = throwable.toString() |
||||
|
// determine the error |
||||
|
val errorMessage: String |
||||
|
val errorCode: String |
||||
|
var connected = false |
||||
|
when (throwable) { |
||||
|
is SpotifyDisconnectedException, is SpotifyConnectionTerminatedException -> { |
||||
|
// The Spotify app was/is disconnected by the Spotify app. |
||||
|
// This indicates typically that the Spotify app was closed by the user or for other reasons. |
||||
|
// You need to reconnect to continue using Spotify App Remote. |
||||
|
errorMessage = |
||||
|
"The Spotify app was/is disconnected by the Spotify app.Reconnect necessary" |
||||
|
errorCode = "SpotifyDisconnectedException" |
||||
|
} |
||||
|
|
||||
|
is CouldNotFindSpotifyApp -> { |
||||
|
errorMessage = "The Spotify app is not installed on the device" |
||||
|
errorCode = "CouldNotFindSpotifyApp" |
||||
|
} |
||||
|
|
||||
|
is AuthenticationFailedException -> { |
||||
|
errorMessage = |
||||
|
"Partner app failed to authenticate with Spotify. Check client credentials and make sure your app is registered correctly at developer.spotify.com" |
||||
|
errorCode = "AuthenticationFailedException" |
||||
|
} |
||||
|
|
||||
|
is UserNotAuthorizedException -> { |
||||
|
errorMessage = |
||||
|
"Indicates the user did not authorize this client of App Remote to use Spotify on the users behalf." |
||||
|
errorCode = "UserNotAuthorizedException" |
||||
|
} |
||||
|
|
||||
|
is UnsupportedFeatureVersionException -> { |
||||
|
errorMessage = |
||||
|
"Spotify app can't support requested features. User should update Spotify app." |
||||
|
errorCode = "UnsupportedFeatureVersionException" |
||||
|
connected = true |
||||
|
} |
||||
|
|
||||
|
is OfflineModeException -> { |
||||
|
errorMessage = |
||||
|
"Spotify user has set their Spotify app to be in offline mode" |
||||
|
errorCode = "OfflineModeException" |
||||
|
connected = true |
||||
|
} |
||||
|
|
||||
|
is NotLoggedInException -> { |
||||
|
errorMessage = "User has logged out from Spotify." |
||||
|
errorCode = "NotLoggedInException" |
||||
|
} |
||||
|
|
||||
|
is SpotifyRemoteServiceException -> { |
||||
|
errorMessage = |
||||
|
"Encapsulates possible SecurityException and IllegalStateException errors." |
||||
|
errorCode = "SpotifyRemoteServiceException" |
||||
|
} |
||||
|
|
||||
|
else -> { |
||||
|
errorMessage = "Something went wrong connecting spotify remote" |
||||
|
errorCode = errorConnection |
||||
|
} |
||||
|
} |
||||
|
Log.e(loggingTag, errorMessage) |
||||
|
// notify plugin |
||||
|
if (initiallyConnected) { |
||||
|
// emit connection error event |
||||
|
// connStatusEventChannel( |
||||
|
// ConnectionStatusChannel.ConnectionEvent( |
||||
|
// connected, |
||||
|
// errorMessage, |
||||
|
// errorCode, |
||||
|
// errorDetails |
||||
|
// ) |
||||
|
// ) |
||||
|
} else { |
||||
|
// throw exception as the connect method |
||||
|
result.error(errorCode, errorMessage, errorDetails) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private fun getAccessToken(clientId: String?, redirectUrl: String?, scope: String?, result: MethodChannel.Result) { |
||||
|
if (applicationActivity == null) { |
||||
|
throw IllegalStateException("getAccessToken needs a foreground activity") |
||||
|
} |
||||
|
|
||||
|
if (clientId.isNullOrBlank() || redirectUrl.isNullOrBlank()) { |
||||
|
result.error(errorConnecting, "client id or redirectUrl are not set or have invalid format", "") |
||||
|
} else { |
||||
|
//Convert String? scope to Array. Delimiter set as comma "," |
||||
|
val scopeArray = scope?.split(",")?.toTypedArray() |
||||
|
// methodConnectToSpotify.checkAndSetPendingOperation(result) |
||||
|
|
||||
|
val builder = AuthorizationRequest.Builder( |
||||
|
clientId, |
||||
|
AuthorizationResponse.Type.TOKEN, |
||||
|
redirectUrl |
||||
|
) |
||||
|
builder.setScopes(scopeArray) |
||||
|
val request = builder.build() |
||||
|
|
||||
|
AuthorizationClient.openLoginActivity( |
||||
|
applicationActivity, |
||||
|
requestCodeAuthentication, |
||||
|
request |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private fun disconnectFromSpotify(result: MethodChannel.Result) { |
||||
|
if (spotifyAppRemote != null && spotifyAppRemote!!.isConnected) { |
||||
|
SpotifyAppRemote.disconnect(spotifyAppRemote) |
||||
|
|
||||
|
// emit connection terminated event |
||||
|
// connStatusEventChannel( |
||||
|
// ConnectionStatusChannel.ConnectionEvent( |
||||
|
// false, |
||||
|
// "Successfully disconnected from Spotify.", |
||||
|
// null, |
||||
|
// null |
||||
|
// ) |
||||
|
// ) |
||||
|
// method success |
||||
|
result.success(true) |
||||
|
} else if (!spotifyAppRemote!!.isConnected) { |
||||
|
result.error(errorDisconnecting, "could not disconnect spotify remote", "you are not connected, no need to disconnect") |
||||
|
} else { |
||||
|
result.error(errorDisconnecting, "could not disconnect spotify remote", "spotifyAppRemote is not set") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private fun play(spotifyUri: String?){ |
||||
|
// if (playerApi != null && !spotifyUri.isNullOrBlank()) { |
||||
|
// playerApi.play(spotifyUri) |
||||
|
// .setResultCallback { result.success(true) } |
||||
|
// .setErrorCallback { throwable -> result.error(errorPlay, "error when playing uri: $spotifyUri", throwable.toString()) } |
||||
|
// } else if (spotifyUri.isNullOrBlank()) { |
||||
|
// result.error(errorPlay, "spotifyUri has invalid format or is not set", "") |
||||
|
// } else { |
||||
|
// spotifyRemoteAppNotSetError() |
||||
|
// } |
||||
|
if (playerApi != null && !spotifyUri.isNullOrBlank()) { |
||||
|
playerApi!!.play(spotifyUri) |
||||
|
} else if (spotifyUri.isNullOrBlank()) { |
||||
|
|
||||
|
} else { |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
private fun pause(){ |
||||
|
// if (playerApi != null) { |
||||
|
// playerApi.pause() |
||||
|
// .setResultCallback { result.success(true) } |
||||
|
// .setErrorCallback { throwable -> result.error(errorPause, "error when pausing", throwable.toString()) } |
||||
|
// } else { |
||||
|
// spotifyRemoteAppNotSetError() |
||||
|
// } |
||||
|
if (playerApi != null) { |
||||
|
playerApi!!.pause() |
||||
|
.setResultCallback {} |
||||
|
.setErrorCallback { } |
||||
|
} else { |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue