From 9633e85cf0c637a84cbc60dc020b5eeff9454afd Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Wed, 23 Apr 2025 14:14:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yunqiinnovation/deepsound/MainActivity.kt | 8 +- .../deepsound/VoiceInteractionHandler.kt | 4 +- .../deepsound/VoiceInteractionService.kt | 6 +- .../services/volcano_translation_service.dart | 133 ++++---- .../spotify_sdk/SpotifySdkService.kt | 311 ++++++++++++++++++ 5 files changed, 401 insertions(+), 61 deletions(-) create mode 100644 local_plugins/spotify_sdk/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkService.kt diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt index ffc3d256a..bded96c8c 100644 --- a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt @@ -173,7 +173,9 @@ class MainActivity: FlutterActivity() { var volcanoSpeechAppId: String = "" var volcanoSpeechAppToken: String = "" var mcpServer: String = "" - + var spotifyClientId: String = "" + var spotifyRedirectUrl: String = "" + // 安全存储相关常量 private const val SECURE_PREFS_FILENAME = "deep_voice_secure_prefs" private const val KEY_AZURE_SPEECH_KEY = "azure_speech_key" @@ -186,6 +188,8 @@ class MainActivity: FlutterActivity() { private const val KEY_VOLCANO_SPEECH_APP_ID = "volcano_speech_app_id" private const val KEY_VOLCANO_SPEECH_APP_TOKEN = "volcano_speech_app_token" private const val KEY_MCP_SERVER = "mcp_server" + private const val KEY_SPOTIFY_CLIENT_ID = "spotify_client_id" + private const val KEY_SPOTIFY_REDIRECT_URL = "spotify_redirect_url" // 会话管理 private const val KEY_SESSION_ID = "session_id" @@ -227,6 +231,8 @@ class MainActivity: FlutterActivity() { .putString(KEY_VOLCANO_SPEECH_APP_ID, volcanoSpeechAppId) .putString(KEY_VOLCANO_SPEECH_APP_TOKEN, volcanoSpeechAppToken) .putString(KEY_MCP_SERVER, mcpServer) + .putString(KEY_SPOTIFY_CLIENT_ID, spotifyClientId) + .putString(KEY_SPOTIFY_REDIRECT_URL, spotifyRedirectUrl) .apply() FileLogger.d("MainActivity", "密钥已安全保存到加密存储中") diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionHandler.kt b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionHandler.kt index 4fd924a36..881b9dc84 100644 --- a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionHandler.kt +++ b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionHandler.kt @@ -26,7 +26,9 @@ class VoiceInteractionHandler( private val openaiModel: String = "", private val volcanoSpeechAppId: String, private val volcanoSpeechAppToken: String, - private val mcpServer: String + private val mcpServer: String, + private val spotifyclientid: String, + private val spotifyRedirectUrl: String ) { private val TAG = "VoiceInteractionHandler" diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionService.kt b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionService.kt index 0497458d2..2822ed511 100644 --- a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionService.kt +++ b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionService.kt @@ -148,6 +148,8 @@ class VoiceInteractionService : Service() { var volcanoSpeechAppId = MainActivity.volcanoSpeechAppId ?: "" var volcanoSpeechAppToken = MainActivity.volcanoSpeechAppToken ?: "" var mcpServer = MainActivity.mcpServer ?: "" + var spotifyclientid = MainActivity.spotifyClientId ?: "" + var spotifyRedirectUrl = MainActivity.spotifyRedirectUrl ?: "" // 如果静态变量中没有配置,尝试从加密存储中加载 if (subscriptionKey.isEmpty() || serviceRegion.isEmpty() || openaiKey.isEmpty()) { @@ -166,6 +168,8 @@ class VoiceInteractionService : Service() { volcanoSpeechAppId = MainActivity.volcanoSpeechAppId ?: "" volcanoSpeechAppToken = MainActivity.volcanoSpeechAppToken ?: "" mcpServer = MainActivity.mcpServer ?: "" + spotifyclientid = MainActivity.spotifyClientId ?: "" + spotifyRedirectUrl = MainActivity.spotifyRedirectUrl ?: "" FileLogger.d(TAG, "已从加密存储加载配置信息") } else { FileLogger.e(TAG, "无法从加密存储加载配置信息") @@ -177,7 +181,7 @@ class VoiceInteractionService : Service() { subscriptionKey, serviceRegion, openaiKey, openaiBaseUrl, openaiModel, volcanoSpeechAppId, volcanoSpeechAppToken, - mcpServer) + mcpServer,spotifyclientid,spotifyRedirectUrl) // 初始化回调 voiceInteractionHandler.setCallback(object : VoiceInteractionHandler.InteractionCallback { diff --git a/lib/data/services/volcano_translation_service.dart b/lib/data/services/volcano_translation_service.dart index 801619d38..11e19bb1c 100644 --- a/lib/data/services/volcano_translation_service.dart +++ b/lib/data/services/volcano_translation_service.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:math'; +import 'package:get_storage/get_storage.dart'; import 'package:http/http.dart' as http; import 'package:crypto/crypto.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -10,7 +11,7 @@ import 'language_manager.dart'; class VolcanoTranslationService extends GetxService { // API 配置 late String _baseUrl; - final String _endpoint = '/'; // 修改为根路径 + final String _endpoint = '/'; // 修改为根路径 late final String _accessKey; late final String _secretKey; late final String _region; @@ -21,7 +22,7 @@ class VolcanoTranslationService extends GetxService { // 语言管理器 final LanguageManager _languageManager = Get.find(); - + final GetStorage _storage = GetStorage(); // 构造函数 VolcanoTranslationService(); @@ -32,9 +33,10 @@ class VolcanoTranslationService extends GetxService { String? region, }) async { try { - _accessKey = accessKey ?? dotenv.env['VOLCANO_TRANSLATION_ACCESS_KEY'] ?? ''; - _secretKey = secretKey ?? dotenv.env['VOLCANO_TRANSLATION_SECRET_KEY'] ?? ''; - _region = region ?? dotenv.env['VOLCANO_TRANSLATION_REGION'] ?? 'cn-north-1'; + final _env = _storage.read("ENV") as Map; + _accessKey = accessKey ?? _env['VOLCANO_TRANSLATION_ACCESS_KEY'] ?? ''; + _secretKey = secretKey ?? _env['VOLCANO_TRANSLATION_SECRET_KEY'] ?? ''; + _region = region ?? _env['VOLCANO_TRANSLATION_REGION'] ?? 'cn-north-1'; _service = 'translate'; _baseUrl = 'https://translate.volcengineapi.com'; @@ -50,14 +52,21 @@ class VolcanoTranslationService extends GetxService { } // 生成签名 - Map _generateSignature(String method, Map requestBody, Map queryParams) { + Map _generateSignature(String method, + Map requestBody, Map queryParams) { // 1. 准备时间相关参数 final now = DateTime.now().toUtc(); final date = now.toIso8601String().split('T')[0].replaceAll('-', ''); - final timestamp = now.toIso8601String().replaceAll(':', '').replaceAll('-', '').split('.')[0] + 'Z'; + final timestamp = now + .toIso8601String() + .replaceAll(':', '') + .replaceAll('-', '') + .split('.')[0] + + 'Z'; // 2. 构建规范查询字符串 - 按照参数名ASCII排序 - final sortedParams = Map.fromEntries(queryParams.entries.toList()..sort((a, b) => a.key.compareTo(b.key))); + final sortedParams = Map.fromEntries( + queryParams.entries.toList()..sort((a, b) => a.key.compareTo(b.key))); final List queryParts = []; sortedParams.forEach((key, value) { // 使用RFC 3986编码,确保特殊字符被正确处理 @@ -66,48 +75,53 @@ class VolcanoTranslationService extends GetxService { queryParts.add('$encodedKey=$encodedValue'); }); final canonicalQueryString = queryParts.join('&'); - + // 3. 创建规范请求 final contentType = 'application/json'; - final payloadHash = sha256.convert(utf8.encode(json.encode(requestBody))).toString(); - + final payloadHash = + sha256.convert(utf8.encode(json.encode(requestBody))).toString(); + final host = 'translate.volcengineapi.com'; - + // 确保头部按照字典序排序,且键名小写 // 注意:只包含host和x-date头部,与示例保持一致 final canonicalHeaders = 'host:$host\nx-date:$timestamp\n'; final signedHeaders = 'host;x-date'; - + // 构建规范请求,格式必须与示例完全一致 - final canonicalRequest = '$method\n$_endpoint\n$canonicalQueryString\n$canonicalHeaders\n$signedHeaders\n$payloadHash'; - + final canonicalRequest = + '$method\n$_endpoint\n$canonicalQueryString\n$canonicalHeaders\n$signedHeaders\n$payloadHash'; + // 4. 创建待签字符串 final algorithm = 'HMAC-SHA256'; final credentialScope = '$date/$_region/$_service/request'; - final stringToSign = '$algorithm\n$timestamp\n$credentialScope\n${sha256.convert(utf8.encode(canonicalRequest)).toString()}'; - + final stringToSign = + '$algorithm\n$timestamp\n$credentialScope\n${sha256.convert(utf8.encode(canonicalRequest)).toString()}'; + // 5. 计算签名 - 按照文档正确派生密钥 // 注意:这里是关键修复点,确保密钥派生过程完全符合文档 - final kSecret = utf8.encode(_secretKey); // 直接使用原始密钥,不添加前缀 - + final kSecret = utf8.encode(_secretKey); // 直接使用原始密钥,不添加前缀 + // kDate = HMAC(kSecret, Date) var kDate = Hmac(sha256, kSecret).convert(utf8.encode(date)).bytes; - + // kRegion = HMAC(kDate, Region) var kRegion = Hmac(sha256, kDate).convert(utf8.encode(_region)).bytes; - + // kService = HMAC(kRegion, Service) var kService = Hmac(sha256, kRegion).convert(utf8.encode(_service)).bytes; - + // kSigning = HMAC(kService, "request") var kSigning = Hmac(sha256, kService).convert(utf8.encode('request')).bytes; - + // 使用派生的签名密钥计算最终签名 - final signature = Hmac(sha256, kSigning).convert(utf8.encode(stringToSign)).toString(); - + final signature = + Hmac(sha256, kSigning).convert(utf8.encode(stringToSign)).toString(); + // 6. 添加签名到请求头 - 格式必须与示例完全一致 - final authorization = '$algorithm Credential=$_accessKey/$credentialScope, SignedHeaders=$signedHeaders, Signature=$signature'; - + final authorization = + '$algorithm Credential=$_accessKey/$credentialScope, SignedHeaders=$signedHeaders, Signature=$signature'; + // 只返回必要的头部,与示例保持一致 return { 'Content-Type': contentType, @@ -131,27 +145,29 @@ class VolcanoTranslationService extends GetxService { // 获取源语言和目标语言的短代码 String sourceCode = ''; String targetCode = 'zh'; - + // 从 ASR 代码获取短代码 if (sourceLanguageCode.isNotEmpty) { - final sourceShortCode = _languageManager.getShortCodeByAsrCode(sourceLanguageCode); + final sourceShortCode = + _languageManager.getShortCodeByAsrCode(sourceLanguageCode); if (sourceShortCode != null) { sourceCode = sourceShortCode; } } - - final targetShortCode = _languageManager.getShortCodeByAsrCode(targetLanguageCode); + + final targetShortCode = + _languageManager.getShortCodeByAsrCode(targetLanguageCode); if (targetShortCode != null) { targetCode = targetShortCode; } - + // 构建请求体 final requestBody = { 'SourceLanguage': sourceCode, 'TargetLanguage': targetCode, 'TextList': [text], }; - + // 添加必要的查询参数 - 确保包含所有必需参数 final queryParams = { 'Action': 'TranslateText', @@ -159,60 +175,61 @@ class VolcanoTranslationService extends GetxService { 'Region': _region, 'Service': _service, }; - + // 生成包含查询参数的签名 final headers = _generateSignature('POST', requestBody, queryParams); - + // 构建带有查询参数的URL final queryString = Uri(queryParameters: queryParams).query; final url = '$_baseUrl/?$queryString'; - + final response = await http.post( Uri.parse(url), headers: headers, body: json.encode(requestBody), ); - + if (response.statusCode == 200) { final responseData = json.decode(response.body); - + // 尝试从不同的响应结构中提取翻译结果 String? translation; - + // 1. 标准响应结构 - if (responseData['TranslationList'] != null && - responseData['TranslationList'] is List && + if (responseData['TranslationList'] != null && + responseData['TranslationList'] is List && responseData['TranslationList'].isNotEmpty) { final translationItem = responseData['TranslationList'][0]; - if (translationItem is Map && translationItem.containsKey('Translation')) { + if (translationItem is Map && + translationItem.containsKey('Translation')) { translation = translationItem['Translation']; } } - + // 2. 检查是否有错误响应 - if (translation == null && - responseData['ResponseMetadata'] != null && - responseData['ResponseMetadata'] is Map && + if (translation == null && + responseData['ResponseMetadata'] != null && + responseData['ResponseMetadata'] is Map && responseData['ResponseMetadata']['Error'] != null) { return null; } - + // 3. 尝试其他可能的响应结构 if (translation == null) { if (responseData.containsKey('Translation')) { translation = responseData['Translation']; - } else if (responseData.containsKey('Result') && - responseData['Result'] is Map && - responseData['Result'].containsKey('Translation')) { + } else if (responseData.containsKey('Result') && + responseData['Result'] is Map && + responseData['Result'].containsKey('Translation')) { translation = responseData['Result']['Translation']; - } else if (responseData.containsKey('Data') && - responseData['Data'] is Map) { + } else if (responseData.containsKey('Data') && + responseData['Data'] is Map) { final data = responseData['Data']; if (data.containsKey('Translation')) { translation = data['Translation']; - } else if (data.containsKey('TranslationList') && - data['TranslationList'] is List && - data['TranslationList'].isNotEmpty) { + } else if (data.containsKey('TranslationList') && + data['TranslationList'] is List && + data['TranslationList'].isNotEmpty) { final item = data['TranslationList'][0]; if (item is Map && item.containsKey('Translation')) { translation = item['Translation']; @@ -220,14 +237,14 @@ class VolcanoTranslationService extends GetxService { } } } - + // 返回提取到的翻译结果 return translation; } - + return null; } catch (e) { return null; } } -} \ No newline at end of file +} diff --git a/local_plugins/spotify_sdk/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkService.kt b/local_plugins/spotify_sdk/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkService.kt new file mode 100644 index 000000000..7449a4b03 --- /dev/null +++ b/local_plugins/spotify_sdk/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkService.kt @@ -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 { + + } + } + + +} \ No newline at end of file