From f776d317efe95a56e737377d41fc868ea121008e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sun, 16 Feb 2020 16:12:59 +0100 Subject: [PATCH 01/47] Update pubspec.yaml --- pubspec.yaml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 1a28e0d1..dcdd118e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,8 @@ environment: dependencies: flutter: sdk: flutter + flutter_web_plugins: + sdk: flutter logger: ^0.8.0 json_annotation: ^3.0.0 @@ -31,8 +33,14 @@ flutter: # be modified. They are used by the tooling to maintain consistency when # adding or updating assets for this project. plugin: - androidPackage: de.minimalme.spotify_sdk - pluginClass: SpotifySdkPlugin + platforms: + android: + package: de.minimalme.spotify_sdk + pluginClass: SpotifySdkPlugin + web: + pluginClass: SpotifySdkPlugin + fileName: spotify_sdk_web.dart + # To add assets to your plugin package, add an assets section, like this: # assets: From dcea3d519a9fafb446f6cea2fd43b6b45bfcb7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sun, 16 Feb 2020 16:49:14 +0100 Subject: [PATCH 02/47] Added web support to example. --- example/.gitignore | 37 +++++++++++++++++++++++++++++++++ example/web/icons/Icon-192.png | Bin 0 -> 4998 bytes example/web/icons/Icon-512.png | Bin 0 -> 14968 bytes example/web/index.html | 30 ++++++++++++++++++++++++++ example/web/manifest.json | 23 ++++++++++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 example/.gitignore create mode 100644 example/web/icons/Icon-192.png create mode 100644 example/web/icons/Icon-512.png create mode 100644 example/web/index.html create mode 100644 example/web/manifest.json diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 00000000..ae1f1838 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,37 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..da5830aabc056befdc1c08d0b8f3fad90c22a051 GIT binary patch literal 4998 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 + + + + + + + + + + + + + example + + + + + + + + diff --git a/example/web/manifest.json b/example/web/manifest.json new file mode 100644 index 00000000..f4bd24b8 --- /dev/null +++ b/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "spotify_sdk", + "short_name": "spotify_sdk", + "start_url": ".", + "display": "minimal-ui", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A flutter plugin that let's you communicate with the spotify sdk and auth lib.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} From 612ad2a820f256722d7753471cdce5e6d0b54872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sun, 16 Feb 2020 23:56:49 +0100 Subject: [PATCH 03/47] Injecting Spotify SDK --- example/web/index.html | 4 +- lib/spotify_sdk_web.dart | 120 +++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 2 + 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 lib/spotify_sdk_web.dart diff --git a/example/web/index.html b/example/web/index.html index 3bc48941..d4340dfe 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -3,7 +3,7 @@ - + @@ -11,7 +11,7 @@ - example + spotify_sdk diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart new file mode 100644 index 00000000..9a3e2002 --- /dev/null +++ b/lib/spotify_sdk_web.dart @@ -0,0 +1,120 @@ +@JS() +library spotify_sdk; + +import 'dart:developer'; +import 'dart:js'; +import 'dart:html'; + +import 'package:flutter/services.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +import 'package:js/js.dart'; + +class SpotifySdkPlugin { + // event streams + static const String CHANNEL_NAME = "spotify_sdk"; + static const String PLAYER_CONTEXT_SUBSCRIPTION = "player_context_subscription"; + static const String PLAYER_STATE_SUBSCRIPTION = "player_state_subscription"; + static const String CAPABILITIES__SUBSCRIPTION = "capabilities_subscription"; + static const String USER_STATUS_SUBSCRIPTION = "user_status_subscription"; + + //static const String playerContextChannel = EventChannel(registrar.messenger(), PLAYER_CONTEXT_SUBSCRIPTION) + //static const String playerStateChannel = EventChannel(registrar.messenger(), PLAYER_STATE_SUBSCRIPTION) + //static const String capabilitiesChannel = EventChannel(registrar.messenger(), CAPABILITIES__SUBSCRIPTION) + //static const String userStatusChannel = EventChannel(registrar.messenger(), USER_STATUS_SUBSCRIPTION) + + // connecting + static const String METHOD_CONNECT_TO_SPOTIFY = "connectToSpotify"; + static const String METHOD_GET_AUTHENTICATION_TOKEN = "getAuthenticationToken"; + static const String METHOD_LOGOUT_FROM_SPOTIFY = "logoutFromSpotify"; + + // player api + static const String METHOD_GET_CROSSFADE_STATE = "getCrossfadeState"; + static const String METHOD_GET_PLAYER_STATE = "getPlayerState"; + static const String METHOD_PLAY = "play"; + static const String METHOD_PAUSE = "pause"; + static const String METHOD_QUEUE_TRACK = "queueTrack"; + static const String METHOD_RESUME = "resume"; + static const String METHOD_SEEK_TO_RELATIVE_POSITION = "seekToRelativePosition"; + static const String METHOD_SET_PODCAST_PLAYBACK_SPEED = "setPodcastPlaybackSpeed"; + static const String METHOD_SKIP_NEXT = "skipNext"; + static const String METHOD_SKIP_PREVIOUS = "skipPrevious"; + static const String METHOD_SKIP_TO_INDEX = "skipToIndex"; + static const String METHOD_SEEK_TO = "seekTo"; + static const String METHOD_TOGGLE_REPEAT = "toggleRepeat"; + static const String METHOD_TOGGLE_SHUFFLE = "toggleShuffle"; + + // user api + static const METHOD_ADD_TO_LIBRARY = "addToLibrary"; + static const METHOD_REMOVE_FROM_LIBRARY = "removeFromLibrary"; + static const METHOD_GET_CAPABILITIES = "getCapabilities"; + static const METHOD_GET_LIBRARY_STATE = "getLibraryState"; + + //images api + static const METHOD_GET_IMAGE = "getImage"; + + static const String PARAM_CLIENT_ID = "clientId"; + static const String PARAM_REDIRECT_URL = "redirectUrl"; + static const String PARAM_SPOTIFY_URI = "spotifyUri"; + static const String PARAM_IMAGE_URI = "imageUri"; + static const String PARAM_IMAGE_DIMENSION = "imageDimension"; + static const String PARAM_POSITIONED_MILLISECONDS = "positionedMilliseconds"; + static const String PARAM_RELATIVE_MILISECONDS = "relativeMilliseconds"; + static const String PARAM_PODCAST_PLAYBACK_SPEED = "podcastPlaybackSpeed"; + static const String PARAM_TRACK_INDEX = "trackIndex"; + + static const String ERROR_CONNECTING = "errorConnecting"; + static const String ERROR_DISCONNECTING = "errorDisconnecting"; + static const String ERROR_AUTHENTICATION_TOKEN_ERROR = "authenticationTokenError"; + + // spotify sdk url + static const String SPOTIFY_SDK_URL = 'https://sdk.scdn.co/spotify-player.js'; + + /*private val requestCodeAuthentication = 1337 + private val scope = arrayOf( + "app-remote-control", + "user-modify-playback-state", + "playlist-read-private", + "playlist-modify-public", + "user-read-currently-playing") + + private var pendingOperation: PendingOperation? = null + private var spotifyAppRemote: SpotifyAppRemote? = null + private var spotifyPlayerApi: SpotifyPlayerApi? = null + private var spotifyUserApi: SpotifyUserApi? = null + private var spotifyImagesApi: SpotifyImagesApi? = null*/ + + SpotifySdkPlugin() { + _initializeSpotify(); + } + + static void registerWith(Registrar registrar) { + final MethodChannel channel = MethodChannel(CHANNEL_NAME, const StandardMethodCodec(), registrar.messenger); + final SpotifySdkPlugin instance = SpotifySdkPlugin(); + channel.setMethodCallHandler(instance.handleMethodCall); + } + + Future handleMethodCall(MethodCall call) async { + log('Method: ${call.method}'); + switch (call.method) { + case METHOD_PAUSE: + log('Starting Spotify playback...'); + break; + default: + throw PlatformException( + code: 'Unimplemented', + details: "The spotify_sdk plugin for web doesn't implement " + "the method '${call.method}'"); + } + } + + /// Loads the Spotify SDK library. + _initializeSpotify() { + context['onSpotifyWebPlaybackSDKReady'] = _onSpotifyInitialized(); + querySelector('body').children.add(ScriptElement()..src=SPOTIFY_SDK_URL); + } + /// Called when the Spotify library is loaded. + _onSpotifyInitialized() { + log('Spotify Initialized!'); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index dcdd118e..9e3f857d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,6 +6,7 @@ issue_tracker: https://github.com/brim-borium/spotify_sdk/issues environment: sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.10.0 <2.0.0" dependencies: flutter: @@ -14,6 +15,7 @@ dependencies: sdk: flutter logger: ^0.8.0 json_annotation: ^3.0.0 + js: ^0.6.1+1 dev_dependencies: flutter_test: From fdb7546d025e9ee92b0ebb64624c0145e3623fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Mon, 17 Feb 2020 16:19:35 +0100 Subject: [PATCH 04/47] Created Dart wrapping around the entire Spotify JS API. Implemented basic connectToSpotify. --- lib/spotify_sdk_web.dart | 329 +++++++++++++++++++++++++++++++++++++-- pubspec.yaml | 1 + 2 files changed, 319 insertions(+), 11 deletions(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 9a3e2002..dfe2c696 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -5,23 +5,25 @@ import 'dart:developer'; import 'dart:js'; import 'dart:html'; +import 'package:dio/dio.dart'; import 'package:flutter/services.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:js/js.dart'; +import 'package:js/js_util.dart'; class SpotifySdkPlugin { - // event streams + // event channels static const String CHANNEL_NAME = "spotify_sdk"; static const String PLAYER_CONTEXT_SUBSCRIPTION = "player_context_subscription"; static const String PLAYER_STATE_SUBSCRIPTION = "player_state_subscription"; static const String CAPABILITIES__SUBSCRIPTION = "capabilities_subscription"; static const String USER_STATUS_SUBSCRIPTION = "user_status_subscription"; - //static const String playerContextChannel = EventChannel(registrar.messenger(), PLAYER_CONTEXT_SUBSCRIPTION) - //static const String playerStateChannel = EventChannel(registrar.messenger(), PLAYER_STATE_SUBSCRIPTION) - //static const String capabilitiesChannel = EventChannel(registrar.messenger(), CAPABILITIES__SUBSCRIPTION) - //static const String userStatusChannel = EventChannel(registrar.messenger(), USER_STATUS_SUBSCRIPTION) + static const EventChannel EVENT_CHANNEL_PLAYER_CONTEXT = EventChannel(PLAYER_CONTEXT_SUBSCRIPTION); + static const EventChannel EVENT_CHANNEL_PLAYER_STATE = EventChannel(PLAYER_STATE_SUBSCRIPTION); + static const EventChannel EVENT_CHANNEL_CAPABILITIES = EventChannel(CAPABILITIES__SUBSCRIPTION); + static const EventChannel EVENT_CHANNEL_USER_STATUS = EventChannel(USER_STATUS_SUBSCRIPTION); // connecting static const String METHOD_CONNECT_TO_SPOTIFY = "connectToSpotify"; @@ -84,6 +86,18 @@ class SpotifySdkPlugin { private var spotifyUserApi: SpotifyUserApi? = null private var spotifyImagesApi: SpotifyImagesApi? = null*/ + /// Dio http client + final Dio _dio = Dio(); + + /// Whether the Spotify SDK was already loaded. + bool _sdkLoaded = false; + /// Current Spotify SDK player instance; + Player _currentPlayer; + /// Current WebPlaybackPlayer instance + WebPlaybackPlayer _currentPlayerConnection; + + static const String ACCESS_TOKEN = 'BQCphbBlirQlququgGwllsUGqX4IJ5lOSAHw86M2xSRmuQAesZvu3TDr4l2y6fH2dYP7yYLLVbFvvrHoXNOgbYWWcPRZbTqZq_6ohbATCrmfytjzy5sYPTHRgyCi5IcesTxAVTb70MFTCUiUfbGPqVdNON0hQOTWO1L1SM2S6yVEMw'; + SpotifySdkPlugin() { _initializeSpotify(); } @@ -95,26 +109,319 @@ class SpotifySdkPlugin { } Future handleMethodCall(MethodCall call) async { - log('Method: ${call.method}'); + // check if spotify is loaded + if(_sdkLoaded == false) { + throw PlatformException( + code: 'Uninitialized', + details: "The Spotify SDK wasn't initialized yet" + ); + } + switch (call.method) { + case METHOD_CONNECT_TO_SPOTIFY: + log('Connecting to Spotify...'); + if(_currentPlayer != null) { + return true; + } + + _currentPlayer = Player( + PlayerOptions( + name: "Test", + getOAuthToken: allowInterop((Function callback, t) { + callback(ACCESS_TOKEN); + }) + ) + ); + + _registerPlayerEvents(_currentPlayer); + var result = await promiseToFuture(_currentPlayer.connect()); + log('Connection result: $result'); + return result; + break; + case METHOD_LOGOUT_FROM_SPOTIFY: + log('Disconnecting from Spotify...'); + if(_currentPlayer == null) { + return false; + } else { + _unregisterPlayerEvents(_currentPlayer); + _currentPlayer.disconnect(); + return true; + } + break; + case METHOD_RESUME: + if(_currentPlayer == null) { + return false; + } else { + await _playTrack('spotify:track:1DMEzmAoQIikcL52psptQL'); + return true; + } + break; case METHOD_PAUSE: - log('Starting Spotify playback...'); + if(_currentPlayer == null) { + return false; + } else { + await promiseToFuture(_currentPlayer.pause()); + return true; + } break; default: throw PlatformException( - code: 'Unimplemented', - details: "The spotify_sdk plugin for web doesn't implement " - "the method '${call.method}'"); + code: 'Unimplemented', + details: "The spotify_sdk plugin for web doesn't implement the method '${call.method}'"); } } + /// Starts track playback in Spotify + _playTrack(String uri) async { + if(_currentPlayerConnection == null) { + log('Spotify player not connected!'); + return; + } + + await _dio.put('https://api.spotify.com/v1/me/player/play', + data: { 'uris': [uri] }, + queryParameters: { + 'device_id': _currentPlayerConnection.device_id + }, + options: Options( + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ${ACCESS_TOKEN}' + }, + ), + ); + } + /// Loads the Spotify SDK library. _initializeSpotify() { - context['onSpotifyWebPlaybackSDKReady'] = _onSpotifyInitialized(); + context['onSpotifyWebPlaybackSDKReady'] = allowInterop(_onSpotifyInitialized); querySelector('body').children.add(ScriptElement()..src=SPOTIFY_SDK_URL); } /// Called when the Spotify library is loaded. _onSpotifyInitialized() { log('Spotify Initialized!'); + _sdkLoaded = true; } + /// Registers Spotify event handlers. + _registerPlayerEvents(Player player) { + // player state + player.addListener( + 'player_state_changed', + allowInterop( + (WebPlaybackState state) { + log('Player state: ${state.track_window.current_track.name}'); + } + ) + ); + + // ready/not ready + player.addListener( + 'ready', + allowInterop( + (WebPlaybackPlayer player) { + log('Device ready! ${player?.device_id}'); + _currentPlayerConnection = player; + } + ) + ); + player.addListener( + 'not_ready', + allowInterop( + (event) { + log('Device not ready!'); + _currentPlayerConnection = null; + } + ) + ); + + // errors + player.addListener( + 'initialization_error', + allowInterop( + (WebPlaybackError error) { + log('initialization_error: ${error.message}'); + } + ) + ); + player.addListener( + 'authentication_error', + allowInterop( + (WebPlaybackError error) { + log('authentication_error: ${error.message}'); + } + ) + ); + player.addListener( + 'account_error', + allowInterop( + (WebPlaybackError error) { + log('account_error: ${error.message}'); + } + ) + ); + player.addListener( + 'playback_error', + allowInterop( + (WebPlaybackError error) { + log('playback_error: ${error.message}'); + } + ) + ); + } + _unregisterPlayerEvents(Player player) { + player.removeListener( + 'player_state_changed' + ); + player.removeListener( + 'ready' + ); + player.removeListener( + 'not_ready' + ); + player.removeListener( + 'initialization_error' + ); + player.removeListener( + 'authentication_error' + ); + player.removeListener( + 'account_error' + ); + player.removeListener( + 'playback_error' + ); + } +} + +/// Spotify Player Object +@JS('Spotify.Player') +class Player { + /// The main constructor for initializing the Web Playback SDK. It should contain an object with the player name, volume and access token. + external Player(PlayerOptions options); + /// Connects Web Playback SDK instance to Spotify with the credentials provided during initialization. + external dynamic connect(); + /// Closes the current session that Web Playback SDK has with Spotify. + external void disconnect(); + /// Create a new event listener in the Web Playback SDK. + external void addListener(String type, Function callback); + /// Remove an event listener in the Web Playback SDK. + external void removeListener(String event_name); + /// Collect metadata on local playback. + external dynamic getCurrentState(); + /// Rename the Spotify Player device. This is visible across all Spotify Connect devices. + external dynamic setName(String name); + /// Set the local volume for the Web Playback SDK. + external dynamic setVolume(double volume); + /// Pause the local playback. + external dynamic pause(); + /// Resume the local playback. + external dynamic resume(); + /// Resume/pause the local playback. + external dynamic togglePlay(); + /// Seek to a position in the current track in local playback. + external dynamic seek(int position_ms); + /// Switch to the previous track in local playback. + external dynamic previousTrack(); + /// Skip to the next track in local playback. + external dynamic nextTrack(); +} +@JS() +@anonymous +class PlayerOptions { + external String get name; + external Function get getOAuthToken; + external double get volume; + + external factory PlayerOptions({String name, Function getOAuthToken, double volume}); +} +@JS('WebPlaybackPlayer') +class WebPlaybackPlayer { + external String get device_id; + + external factory WebPlaybackPlayer({String device_id}); +} +@JS('WebPlaybackState') +class WebPlaybackState { + external WebPlayerContext get context; + external WebPlayerDisallows get disallows; + external bool get paused; + external int get position; + external int get repeat_mode; + external bool get shuffle; + external WebPlayerTrackWindow get track_window; + + external factory WebPlaybackState({WebPlayerContext context, WebPlayerDisallows disallows, bool paysed, int position, int repeat_mode, bool shuffle, WebPlayerTrackWindow track_window}); +} +@JS() +@anonymous +class WebPlayerContext { + external String get uri; + external Map get metadata; + + external factory WebPlayerContext({String uri, Map metadata}); +} +@JS() +@anonymous +class WebPlayerDisallows { + external bool get pausing; + external bool get peeking_next; + external bool get peeking_prev; + external bool get resuming; + external bool get seeking; + external bool get skipping_next; + external bool get skipping_prev; + + external factory WebPlayerDisallows({bool pausing, bool peeking_next, bool peeking_prev, bool resuming, bool seeking, bool skipping_next, bool skipping_prev}); +} +@JS() +@anonymous +class WebPlayerTrackWindow { + external WebPlaybackTrack get current_track; + external List get previous_tracks; + external List get next_tracks; + + external factory WebPlayerTrackWindow({WebPlaybackTrack current_track, List previous_tracks, List next_tracks}); +} +@JS('WebPlaybackTrack') +class WebPlaybackTrack { + external String get uri; + external String get id; + external String get type; + external String get media_type; + external String get name; + external bool get is_playable; + external WebPlaybackAlbum get album; + external List get artists; + + external factory WebPlaybackTrack({String uri, String id, String type, String media_type, String name, bool is_playable, WebPlaybackAlbum album, List artists}); +} +@JS() +@anonymous +class WebPlaybackAlbum { + external String get uri; + external String get name; + external List get images; + + external factory WebPlaybackAlbum({String uri, String name, List images}); +} +@JS() +@anonymous +class WebPlaybackArtist { + external String get uri; + external String get name; + + external factory WebPlaybackArtist({String uri, String name}); +} +@JS() +@anonymous +class WebPlaybackAlbumImage { + external String get url; + + external factory WebPlaybackAlbumImage({String url}); +} +@JS('WebPlaybackError') +class WebPlaybackError { + external String get message; + + external factory WebPlaybackError({String message}); } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 9e3f857d..917f087d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: flutter_web_plugins: sdk: flutter logger: ^0.8.0 + dio: any json_annotation: ^3.0.0 js: ^0.6.1+1 From c5eacf01b6f7784a25a861c490d194da5d0c89bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Mon, 17 Feb 2020 16:22:30 +0100 Subject: [PATCH 05/47] Update spotify_sdk_web.dart --- lib/spotify_sdk_web.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index dfe2c696..efd5569e 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -334,13 +334,15 @@ class PlayerOptions { external factory PlayerOptions({String name, Function getOAuthToken, double volume}); } -@JS('WebPlaybackPlayer') +@JS() +@anonymous class WebPlaybackPlayer { external String get device_id; external factory WebPlaybackPlayer({String device_id}); } -@JS('WebPlaybackState') +@JS() +@anonymous class WebPlaybackState { external WebPlayerContext get context; external WebPlayerDisallows get disallows; From ba64befedd0b07e00b63b997b8f15eb6c738cd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Tue, 18 Feb 2020 16:05:01 +0100 Subject: [PATCH 06/47] Implemented Spotify authentication flow. --- lib/spotify_sdk_web.dart | 126 ++++++++++++++++++++++++++++++++------- pubspec.yaml | 1 + 2 files changed, 106 insertions(+), 21 deletions(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index efd5569e..315a4565 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -7,6 +7,7 @@ import 'dart:html'; import 'package:dio/dio.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:js/js.dart'; @@ -72,15 +73,17 @@ class SpotifySdkPlugin { // spotify sdk url static const String SPOTIFY_SDK_URL = 'https://sdk.scdn.co/spotify-player.js'; - /*private val requestCodeAuthentication = 1337 - private val scope = arrayOf( - "app-remote-control", - "user-modify-playback-state", - "playlist-read-private", - "playlist-modify-public", - "user-read-currently-playing") - - private var pendingOperation: PendingOperation? = null + // auth + static const int AUTHENTICATION_REQUEST_CODE = 1337; + static const List AUTHENTICATION_SCOPES = [ + "app-remote-control", + "user-modify-playback-state", + "playlist-read-private", + "playlist-modify-public", + "user-read-currently-playing" + ]; + + /*private var pendingOperation: PendingOperation? = null private var spotifyAppRemote: SpotifyAppRemote? = null private var spotifyPlayerApi: SpotifyPlayerApi? = null private var spotifyUserApi: SpotifyUserApi? = null @@ -91,12 +94,16 @@ class SpotifySdkPlugin { /// Whether the Spotify SDK was already loaded. bool _sdkLoaded = false; - /// Current Spotify SDK player instance; + /// Current Spotify SDK player instance. Player _currentPlayer; - /// Current WebPlaybackPlayer instance + /// Current WebPlaybackPlayer instance. WebPlaybackPlayer _currentPlayerConnection; - - static const String ACCESS_TOKEN = 'BQCphbBlirQlququgGwllsUGqX4IJ5lOSAHw86M2xSRmuQAesZvu3TDr4l2y6fH2dYP7yYLLVbFvvrHoXNOgbYWWcPRZbTqZq_6ohbATCrmfytjzy5sYPTHRgyCi5IcesTxAVTb70MFTCUiUfbGPqVdNON0hQOTWO1L1SM2S6yVEMw'; + /// Current Spotify auth token. + SpotifyToken _spotifyToken; + /// Cached client id used when connecting to Spotify. + String cachedClientId; + /// Cached redirect url used when connecting to Spotify. + String cachedRedirectUrl; SpotifySdkPlugin() { _initializeSpotify(); @@ -123,20 +130,32 @@ class SpotifySdkPlugin { if(_currentPlayer != null) { return true; } + // update the client id and redirect url + String clientId = call.arguments[PARAM_CLIENT_ID]; + String redirectUrl = call.arguments[PARAM_REDIRECT_URL]; + if (!(clientId?.isNotEmpty == true && redirectUrl?.isNotEmpty == true)) { + throw PlatformException(message: "client id or redirectUrl are not set or have invalid format", code: ""); + } + cachedClientId = clientId; + cachedRedirectUrl = redirectUrl; + // get initial token + await _getSpotifyAuthToken(); + + // create player _currentPlayer = Player( PlayerOptions( name: "Test", getOAuthToken: allowInterop((Function callback, t) { - callback(ACCESS_TOKEN); + _getSpotifyAuthToken().then((value) { + callback(value); + }); }) ) ); _registerPlayerEvents(_currentPlayer); - var result = await promiseToFuture(_currentPlayer.connect()); - log('Connection result: $result'); - return result; + return await promiseToFuture(_currentPlayer.connect()); break; case METHOD_LOGOUT_FROM_SPOTIFY: log('Disconnecting from Spotify...'); @@ -152,7 +171,7 @@ class SpotifySdkPlugin { if(_currentPlayer == null) { return false; } else { - await _playTrack('spotify:track:1DMEzmAoQIikcL52psptQL'); + await promiseToFuture(_currentPlayer.resume()); return true; } break; @@ -186,7 +205,7 @@ class SpotifySdkPlugin { options: Options( headers: { 'Content-Type': 'application/json', - 'Authorization': 'Bearer ${ACCESS_TOKEN}' + 'Authorization': 'Bearer ${await _getSpotifyAuthToken()}' }, ), ); @@ -291,6 +310,63 @@ class SpotifySdkPlugin { 'playback_error' ); } + /// Gets the current Spotify token or reauthenticates the user if the token expired. + Future _getSpotifyAuthToken({String clientId, String redirectUrl}) async { + if(_spotifyToken != null && _spotifyToken.expiry > DateTime.now().millisecondsSinceEpoch) { + return _spotifyToken.token; + } + + if(clientId == null) { + clientId = cachedClientId; + } + if(redirectUrl == null) { + redirectUrl = cachedRedirectUrl; + } + String newToken = await _authenticateSpotify(clientId, redirectUrl); + _spotifyToken = SpotifyToken(newToken, DateTime.now().millisecondsSinceEpoch + 3600000); + return _spotifyToken.token; + } + /// Authenticates the user and returns the access token on success. + Future _authenticateSpotify(String clientId, String redirectUrl) async { + if (clientId?.isNotEmpty == true && redirectUrl?.isNotEmpty == true) { + String scopes = AUTHENTICATION_SCOPES.join(' '); + String authUrl = 'https://accounts.spotify.com/authorize?client_id=$clientId&response_type=token&scope=$scopes&redirect_uri=$redirectUrl'; + + WindowBase authPopup = window.open(authUrl, "Spotify Authorization"); + String hash; + String error; + var sub = window.onMessage.listen(allowInterop((event) { + String message = event.data.toString(); + if(message.startsWith('#')) { + log('Hash received: ${event.data}'); + hash = message; + } else if (message.startsWith('?')) { + log('Authorization error: ${event.data}'); + error = message; + } + })); + + // loop and wait for auth + while(authPopup.closed == false && hash == null && error == null) { + // await response from the window + await Future.delayed(Duration(milliseconds: 250)); + } + + // cleanup + if(authPopup.closed == false) { + authPopup.close(); + } + await sub.cancel(); + + // check output + if(error != null || hash == null) { + throw PlatformException(message: "authorization error: $error", code: ""); + } + return hash.split('&')[0].split('=')[1]; + } else { + throw PlatformException(message: "client id or redirectUrl are not set or have invalid format", code: ""); + } + } } /// Spotify Player Object @@ -384,7 +460,8 @@ class WebPlayerTrackWindow { external factory WebPlayerTrackWindow({WebPlaybackTrack current_track, List previous_tracks, List next_tracks}); } -@JS('WebPlaybackTrack') +@JS() +@anonymous class WebPlaybackTrack { external String get uri; external String get id; @@ -421,9 +498,16 @@ class WebPlaybackAlbumImage { external factory WebPlaybackAlbumImage({String url}); } -@JS('WebPlaybackError') +@JS() +@anonymous class WebPlaybackError { external String get message; external factory WebPlaybackError({String message}); +} +class SpotifyToken { + final String token; + final int expiry; + + SpotifyToken(this.token, this.expiry); } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 917f087d..74e0d6f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: dio: any json_annotation: ^3.0.0 js: ^0.6.1+1 + oauth2: ^1.5.0 dev_dependencies: flutter_test: From bc57419219e720a398e9843d7f8d6a4dfddbd745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Tue, 18 Feb 2020 19:09:14 +0100 Subject: [PATCH 07/47] Implemented player state event and play method. --- lib/spotify_sdk_web.dart | 242 ++++++++++++++++++++++++++++++--------- 1 file changed, 185 insertions(+), 57 deletions(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 315a4565..b531ec22 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -1,31 +1,34 @@ @JS() library spotify_sdk; +import 'dart:async'; +import 'dart:convert'; import 'dart:developer'; import 'dart:js'; import 'dart:html'; import 'package:dio/dio.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:js/js.dart'; import 'package:js/js_util.dart'; +import 'package:spotify_sdk/models/album.dart'; +import 'package:spotify_sdk/models/artist.dart'; +import 'package:spotify_sdk/models/image_uri.dart'; +import 'package:spotify_sdk/models/player_options.dart' as options; +import 'package:spotify_sdk/models/player_restrictions.dart'; +import 'package:spotify_sdk/models/player_state.dart'; +import 'package:spotify_sdk/models/track.dart'; class SpotifySdkPlugin { // event channels static const String CHANNEL_NAME = "spotify_sdk"; static const String PLAYER_CONTEXT_SUBSCRIPTION = "player_context_subscription"; static const String PLAYER_STATE_SUBSCRIPTION = "player_state_subscription"; - static const String CAPABILITIES__SUBSCRIPTION = "capabilities_subscription"; + static const String PLAYER_CAPABILITIES_SUBSCRIPTION = "capabilities_subscription"; static const String USER_STATUS_SUBSCRIPTION = "user_status_subscription"; - static const EventChannel EVENT_CHANNEL_PLAYER_CONTEXT = EventChannel(PLAYER_CONTEXT_SUBSCRIPTION); - static const EventChannel EVENT_CHANNEL_PLAYER_STATE = EventChannel(PLAYER_STATE_SUBSCRIPTION); - static const EventChannel EVENT_CHANNEL_CAPABILITIES = EventChannel(CAPABILITIES__SUBSCRIPTION); - static const EventChannel EVENT_CHANNEL_USER_STATUS = EventChannel(USER_STATUS_SUBSCRIPTION); - // connecting static const String METHOD_CONNECT_TO_SPOTIFY = "connectToSpotify"; static const String METHOD_GET_AUTHENTICATION_TOKEN = "getAuthenticationToken"; @@ -74,7 +77,6 @@ class SpotifySdkPlugin { static const String SPOTIFY_SDK_URL = 'https://sdk.scdn.co/spotify-player.js'; // auth - static const int AUTHENTICATION_REQUEST_CODE = 1337; static const List AUTHENTICATION_SCOPES = [ "app-remote-control", "user-modify-playback-state", @@ -83,21 +85,10 @@ class SpotifySdkPlugin { "user-read-currently-playing" ]; - /*private var pendingOperation: PendingOperation? = null - private var spotifyAppRemote: SpotifyAppRemote? = null - private var spotifyPlayerApi: SpotifyPlayerApi? = null - private var spotifyUserApi: SpotifyUserApi? = null - private var spotifyImagesApi: SpotifyImagesApi? = null*/ - - /// Dio http client - final Dio _dio = Dio(); - /// Whether the Spotify SDK was already loaded. bool _sdkLoaded = false; /// Current Spotify SDK player instance. Player _currentPlayer; - /// Current WebPlaybackPlayer instance. - WebPlaybackPlayer _currentPlayerConnection; /// Current Spotify auth token. SpotifyToken _spotifyToken; /// Cached client id used when connecting to Spotify. @@ -105,13 +96,35 @@ class SpotifySdkPlugin { /// Cached redirect url used when connecting to Spotify. String cachedRedirectUrl; - SpotifySdkPlugin() { + // Event stream controllers + final StreamController playerContextEventController; + final StreamController playerStateEventController; + final StreamController playerCapabilitiesEventController; + final StreamController userStateEventController; + + SpotifySdkPlugin(this.playerContextEventController, this.playerStateEventController, this.playerCapabilitiesEventController, this.userStateEventController) { _initializeSpotify(); } static void registerWith(Registrar registrar) { + // method channel final MethodChannel channel = MethodChannel(CHANNEL_NAME, const StandardMethodCodec(), registrar.messenger); - final SpotifySdkPlugin instance = SpotifySdkPlugin(); + // event channels + final PluginEventChannel playerContextEventChannel = PluginEventChannel(PLAYER_CONTEXT_SUBSCRIPTION); + final StreamController playerContextEventController = StreamController.broadcast(); + playerContextEventChannel.controller = playerContextEventController; + final PluginEventChannel playerStateEventChannel = PluginEventChannel(PLAYER_STATE_SUBSCRIPTION); + final StreamController playerStateEventController = StreamController.broadcast(); + playerStateEventChannel.controller = playerStateEventController; + final PluginEventChannel playerCapabilitiesEventChannel = PluginEventChannel(PLAYER_CAPABILITIES_SUBSCRIPTION); + final StreamController playerCapabilitiesEventController = StreamController.broadcast(); + playerCapabilitiesEventChannel.controller = playerCapabilitiesEventController; + final PluginEventChannel userStatusEventChannel = PluginEventChannel(USER_STATUS_SUBSCRIPTION); + final StreamController userStatusEventController = StreamController.broadcast(); + userStatusEventChannel.controller = userStatusEventController; + + final SpotifySdkPlugin instance = SpotifySdkPlugin(playerContextEventController, playerStateEventController, playerCapabilitiesEventController, userStatusEventController); + channel.setMethodCallHandler(instance.handleMethodCall); } @@ -157,6 +170,9 @@ class SpotifySdkPlugin { _registerPlayerEvents(_currentPlayer); return await promiseToFuture(_currentPlayer.connect()); break; + case METHOD_GET_AUTHENTICATION_TOKEN: + return await _getSpotifyAuthToken(clientId: call.arguments[PARAM_CLIENT_ID], redirectUrl: call.arguments[PARAM_REDIRECT_URL]); + break; case METHOD_LOGOUT_FROM_SPOTIFY: log('Disconnecting from Spotify...'); if(_currentPlayer == null) { @@ -167,21 +183,33 @@ class SpotifySdkPlugin { return true; } break; + case METHOD_PLAY: + await promiseToFuture(_currentPlayer?.play(call.arguments[PARAM_SPOTIFY_URI], await _getSpotifyAuthToken())); + break; case METHOD_RESUME: - if(_currentPlayer == null) { - return false; - } else { - await promiseToFuture(_currentPlayer.resume()); - return true; - } + await promiseToFuture(_currentPlayer?.resume()); break; case METHOD_PAUSE: - if(_currentPlayer == null) { - return false; - } else { - await promiseToFuture(_currentPlayer.pause()); - return true; - } + await promiseToFuture(_currentPlayer?.pause()); + break; + case METHOD_SKIP_NEXT: + await promiseToFuture(_currentPlayer?.nextTrack()); + break; + case METHOD_SKIP_PREVIOUS: + await promiseToFuture(_currentPlayer?.previousTrack()); + break; + /*case METHOD_TOGGLE_SHUFFLE: + //TODO: Needs a state parameter (true/false) + //await _currentPlayer?.toggleShuffle(state, await _getSpotifyAuthToken()); + break;*/ + /*case METHOD_TOGGLE_REPEAT: + //TODO: Needs a state parameter (true/false) + //await _currentPlayer?.toggleRepeat(state, await _getSpotifyAuthToken()); + break;*/ + case METHOD_GET_PLAYER_STATE: + WebPlaybackState stateRaw = await promiseToFuture(_currentPlayer?.getCurrentState()); + if(stateRaw == null) return null; + return jsonEncode(toPlayerState(stateRaw).toJson()); break; default: throw PlatformException( @@ -190,27 +218,6 @@ class SpotifySdkPlugin { } } - /// Starts track playback in Spotify - _playTrack(String uri) async { - if(_currentPlayerConnection == null) { - log('Spotify player not connected!'); - return; - } - - await _dio.put('https://api.spotify.com/v1/me/player/play', - data: { 'uris': [uri] }, - queryParameters: { - 'device_id': _currentPlayerConnection.device_id - }, - options: Options( - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer ${await _getSpotifyAuthToken()}' - }, - ), - ); - } - /// Loads the Spotify SDK library. _initializeSpotify() { context['onSpotifyWebPlaybackSDKReady'] = allowInterop(_onSpotifyInitialized); @@ -228,7 +235,8 @@ class SpotifySdkPlugin { 'player_state_changed', allowInterop( (WebPlaybackState state) { - log('Player state: ${state.track_window.current_track.name}'); + if(state == null) return; + playerStateEventController.add(jsonEncode(toPlayerState(state).toJson())); } ) ); @@ -239,7 +247,7 @@ class SpotifySdkPlugin { allowInterop( (WebPlaybackPlayer player) { log('Device ready! ${player?.device_id}'); - _currentPlayerConnection = player; + _currentPlayer.deviceID = player.device_id; } ) ); @@ -248,7 +256,7 @@ class SpotifySdkPlugin { allowInterop( (event) { log('Device not ready!'); - _currentPlayerConnection = null; + _currentPlayer.deviceID = null; } ) ); @@ -367,11 +375,71 @@ class SpotifySdkPlugin { throw PlatformException(message: "client id or redirectUrl are not set or have invalid format", code: ""); } } + PlayerState toPlayerState(WebPlaybackState state) { + if(state == null) return null; + WebPlaybackTrack trackRaw = state.track_window?.current_track; + WebPlaybackAlbum albumRaw = trackRaw?.album; + WebPlayerDisallows restrictionsRaw = state.disallows; + List artists = []; + for(var artist in trackRaw.artists) { + artists.add(Artist(artist.name, artist.uri)); + } + + // getting repeat mode + options.RepeatMode repeatMode; + switch (state.repeat_mode) { + case 1: + repeatMode = options.RepeatMode.Context; + break; + case 2: + repeatMode = options.RepeatMode.Track; + break; + default: + repeatMode = options.RepeatMode.Off; + break; + } + + return PlayerState( + trackRaw != null + ? Track ( + Album( + albumRaw.name, + albumRaw.uri + ), + artists[0], + artists, + null, + ImageUri(albumRaw.images[0].url), + false, + false, + trackRaw.name, + trackRaw.uri + ) + : null, + state.paused, + 1.0, + state.position, + options.PlayerOptions(state.shuffle, repeatMode), + PlayerRestrictions( + restrictionsRaw.skipping_next, + restrictionsRaw.skipping_prev, + false, + false, + false, + restrictionsRaw.seeking + ) + ); + } } /// Spotify Player Object @JS('Spotify.Player') class Player { + /// Dio http client + final Dio _dio = Dio(); + /// Device id of the player. + String deviceID; + /// The main constructor for initializing the Web Playback SDK. It should contain an object with the player name, volume and access token. external Player(PlayerOptions options); /// Connects Web Playback SDK instance to Spotify with the credentials provided during initialization. @@ -400,6 +468,66 @@ class Player { external dynamic previousTrack(); /// Skip to the next track in local playback. external dynamic nextTrack(); + /// Starts track playback on the device. + play(String uri, String accessToken) async { + if(deviceID == null) { + log('Spotify player not connected!'); + return; + } + + await _dio.put('https://api.spotify.com/v1/me/player/play', + data: { 'uris': [uri] }, + queryParameters: { + 'device_id': deviceID + }, + options: Options( + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $accessToken' + }, + ), + ); + } + /// Toggles shuffle on the current player. + toggleShuffle(bool state, String accessToken) async { + if(deviceID == null) { + log('Spotify player not connected!'); + return; + } + + await _dio.put('https://api.spotify.com/v1/me/player/shuffle', + queryParameters: { + 'state': state, + 'device_id': deviceID + }, + options: Options( + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $accessToken' + }, + ), + ); + } + /// Toggles repeat on the current player. + toggleRepeat(bool state, String accessToken) async { + if(deviceID == null) { + log('Spotify player not connected!'); + return; + } + + await _dio.put('https://api.spotify.com/v1/me/player/repeat', + queryParameters: { + 'state': state, + 'device_id': deviceID + }, + options: Options( + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $accessToken' + }, + ), + ); + } } @JS() @anonymous From 3041f276c927617f1e1c23dba7ccd5306c14072c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Tue, 18 Feb 2020 19:24:12 +0100 Subject: [PATCH 08/47] Update spotify_sdk_web.dart --- lib/spotify_sdk_web.dart | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index b531ec22..5e6469cc 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -16,6 +16,7 @@ import 'package:js/js_util.dart'; import 'package:spotify_sdk/models/album.dart'; import 'package:spotify_sdk/models/artist.dart'; import 'package:spotify_sdk/models/image_uri.dart'; +import 'package:spotify_sdk/models/player_context.dart'; import 'package:spotify_sdk/models/player_options.dart' as options; import 'package:spotify_sdk/models/player_restrictions.dart'; import 'package:spotify_sdk/models/player_state.dart'; @@ -237,6 +238,7 @@ class SpotifySdkPlugin { (WebPlaybackState state) { if(state == null) return; playerStateEventController.add(jsonEncode(toPlayerState(state).toJson())); + playerContextEventController.add(jsonEncode(toPlayerContext(state).toJson())); } ) ); @@ -375,6 +377,7 @@ class SpotifySdkPlugin { throw PlatformException(message: "client id or redirectUrl are not set or have invalid format", code: ""); } } + /// Converts a native WebPlaybackState to the library PlayerState PlayerState toPlayerState(WebPlaybackState state) { if(state == null) return null; WebPlaybackTrack trackRaw = state.track_window?.current_track; @@ -430,6 +433,17 @@ class SpotifySdkPlugin { ) ); } + /// Converts a native WebPlaybackState to the library PlayerContext + PlayerContext toPlayerContext(WebPlaybackState state) { + if(state == null) return null; + log(state.context.metadata.title); + return PlayerContext( + state.context.metadata.title, + state.context.metadata.subtitle, + state.context.metadata.type, + state.context.uri + ); + } } /// Spotify Player Object @@ -562,9 +576,18 @@ class WebPlaybackState { @anonymous class WebPlayerContext { external String get uri; - external Map get metadata; + external WebPlayerContextMetadata get metadata; + + external factory WebPlayerContext({String uri, WebPlayerContextMetadata metadata}); +} +@JS() +@anonymous +class WebPlayerContextMetadata { + external String get title; + external String get subtitle; + external String get type; - external factory WebPlayerContext({String uri, Map metadata}); + external factory WebPlayerContextMetadata({String title, String subtitle, String type}); } @JS() @anonymous From 16f0795f9c3597e946c651aa04f302c30dab690b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Tue, 18 Feb 2020 19:46:02 +0100 Subject: [PATCH 09/47] cleanup --- lib/spotify_sdk_web.dart | 10 +++++++--- pubspec.yaml | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 5e6469cc..dd1e88f4 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -436,7 +436,6 @@ class SpotifySdkPlugin { /// Converts a native WebPlaybackState to the library PlayerContext PlayerContext toPlayerContext(WebPlaybackState state) { if(state == null) return null; - log(state.context.metadata.title); return PlayerContext( state.context.metadata.title, state.context.metadata.subtitle, @@ -450,7 +449,12 @@ class SpotifySdkPlugin { @JS('Spotify.Player') class Player { /// Dio http client - final Dio _dio = Dio(); + final Dio _dio = Dio( + BaseOptions( + baseUrl: 'https://api.spotify.com/v1/me/player', + ) + ); + /// Device id of the player. String deviceID; @@ -489,7 +493,7 @@ class Player { return; } - await _dio.put('https://api.spotify.com/v1/me/player/play', + await _dio.put('/play', data: { 'uris': [uri] }, queryParameters: { 'device_id': deviceID diff --git a/pubspec.yaml b/pubspec.yaml index 74e0d6f6..917f087d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,6 @@ dependencies: dio: any json_annotation: ^3.0.0 js: ^0.6.1+1 - oauth2: ^1.5.0 dev_dependencies: flutter_test: From b1dd440ce19197e47c1ab4d6eb1b8237c3dba016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Tue, 18 Feb 2020 20:01:18 +0100 Subject: [PATCH 10/47] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 94d17ead..6fc9b425 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This will be a spotify_sdk package for flutter using both the spotify-app-remote From the [Spotify Android SDK Quick Start](https://developer.spotify.com/documentation/android/quick-start/). You need two things: -1. register your app in the [spotify developer portal](https://developer.spotify.com/dashboard/). You also need to create a sha-1 fingerprint and add this and your package name to the app settings on the dashboard as well as a redirect url. +1. Register your app in the [spotify developer portal](https://developer.spotify.com/dashboard/). You also need to create a sha-1 fingerprint and add this and your package name to the app settings on the dashboard as well as a redirect url. 2. download the current [Spotify Android SDK](https://github.com/spotify/android-sdk/releases). Here you need the spotify-app-remote-*.aar and spotify-auth-*.aar. After you are all setup you need to add the *.aar files to your Android Project as Modules. See the [Spotify Android SDK Quick Start](https://developer.spotify.com/documentation/android/quick-start/) for detailed information. @@ -23,6 +23,14 @@ Important here is the naming so that the package can find the modules. - Remote: spotify-app-remote - Auth: spotify-auth +### Web + +1. Register your app in the [spotify developer portal](https://developer.spotify.com/dashboard/). You need to provide a redirect URL which points to a dedicated page on a website you own. + +2. Paste the following onto the webpage, which you linked to in your redirect URL. + +[You need Spotify Premium to access the Web SDK.](https://developer.spotify.com/documentation/web-playback-sdk/quick-start/) + ## Usage To start using this package you first have to connect to Spotify. To only connect you can do this with connectToSpotifyRemote(...) or getAuthenticationToken(...) in both of these methods you need the client id, given in the spotify dashboard and the redirect url you set in the settings on the dashboard. From 98e7690b525e2bdaee7d010e4e5498a2915ffef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Tue, 18 Feb 2020 20:14:58 +0100 Subject: [PATCH 11/47] Update README.md --- README.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6fc9b425..41e51fa9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,40 @@ Important here is the naming so that the package can find the modules. 1. Register your app in the [spotify developer portal](https://developer.spotify.com/dashboard/). You need to provide a redirect URL which points to a dedicated page on a website you own. -2. Paste the following onto the webpage, which you linked to in your redirect URL. +2. Paste the following onto the webpage, which you linked to in your redirect URL. +```html + + + + Authenticating Spotify + + +

Please wait while we authenticate Spotify...

+ + + +``` [You need Spotify Premium to access the Web SDK.](https://developer.spotify.com/documentation/web-playback-sdk/quick-start/) From d9e89b2a378386d85de30666a957ebc7b724c8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Tue, 18 Feb 2020 20:27:56 +0100 Subject: [PATCH 12/47] Added playerName paramaters to connectToSpotify --- lib/spotify_sdk.dart | 3 ++- lib/spotify_sdk_web.dart | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/spotify_sdk.dart b/lib/spotify_sdk.dart index 9d7118c2..57e145c7 100644 --- a/lib/spotify_sdk.dart +++ b/lib/spotify_sdk.dart @@ -54,6 +54,7 @@ class SpotifySdk { // params static const String _paramClientId = "clientId"; static const String _paramRedirectUrl = "redirectUrl"; + static const String _paramPlayerName = "playerName"; static const String _paramSpotifyUri = "spotifyUri"; static const String _paramImageUri = "imageUri"; static const String _paramImageDimension = "imageDimension"; @@ -68,7 +69,7 @@ class SpotifySdk { /// Throws a [PlatformException] if connecting to the remote api failed /// Throws a [MissingPluginException] if the method is not implemented on the native platforms. static Future connectToSpotifyRemote( - {@required String clientId, @required String redirectUrl}) async { + {@required String clientId, @required String redirectUrl, String playerName = 'Spotify SDK'}) async { try { return await _channel.invokeMethod(_methodConnectToSpotify, {_paramClientId: clientId, _paramRedirectUrl: redirectUrl}); diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index dd1e88f4..1c57f064 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -62,6 +62,7 @@ class SpotifySdkPlugin { static const String PARAM_CLIENT_ID = "clientId"; static const String PARAM_REDIRECT_URL = "redirectUrl"; + static const String PARAM_PLAYER_NAME = "playerName"; static const String PARAM_SPOTIFY_URI = "spotifyUri"; static const String PARAM_IMAGE_URI = "imageUri"; static const String PARAM_IMAGE_DIMENSION = "imageDimension"; @@ -147,8 +148,9 @@ class SpotifySdkPlugin { // update the client id and redirect url String clientId = call.arguments[PARAM_CLIENT_ID]; String redirectUrl = call.arguments[PARAM_REDIRECT_URL]; + String playerName = call.arguments[PARAM_PLAYER_NAME]; if (!(clientId?.isNotEmpty == true && redirectUrl?.isNotEmpty == true)) { - throw PlatformException(message: "client id or redirectUrl are not set or have invalid format", code: ""); + throw PlatformException(message: "Client id or redirectUrl are not set or have invalid format", code: "Authentication Error"); } cachedClientId = clientId; cachedRedirectUrl = redirectUrl; @@ -159,7 +161,7 @@ class SpotifySdkPlugin { // create player _currentPlayer = Player( PlayerOptions( - name: "Test", + name: playerName, getOAuthToken: allowInterop((Function callback, t) { _getSpotifyAuthToken().then((value) { callback(value); @@ -370,11 +372,11 @@ class SpotifySdkPlugin { // check output if(error != null || hash == null) { - throw PlatformException(message: "authorization error: $error", code: ""); + throw PlatformException(message: "$error", code: "Authentication Error"); } return hash.split('&')[0].split('=')[1]; } else { - throw PlatformException(message: "client id or redirectUrl are not set or have invalid format", code: ""); + throw PlatformException(message: "Client id or redirectUrl are not set or have invalid format", code: "Authentication Error"); } } /// Converts a native WebPlaybackState to the library PlayerState From 4aecd60b26ec7b07d7d91c50f7aa9c7fabf34072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Tue, 18 Feb 2020 20:34:22 +0100 Subject: [PATCH 13/47] Update spotify_sdk.dart --- lib/spotify_sdk.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spotify_sdk.dart b/lib/spotify_sdk.dart index 57e145c7..643faf41 100644 --- a/lib/spotify_sdk.dart +++ b/lib/spotify_sdk.dart @@ -72,7 +72,7 @@ class SpotifySdk { {@required String clientId, @required String redirectUrl, String playerName = 'Spotify SDK'}) async { try { return await _channel.invokeMethod(_methodConnectToSpotify, - {_paramClientId: clientId, _paramRedirectUrl: redirectUrl}); + {_paramClientId: clientId, _paramRedirectUrl: redirectUrl, _paramPlayerName: playerName}); } on Exception catch (e) { _logException(_methodConnectToSpotify, e); rethrow; From a4feed6a4d19ada3ec1363b71f925cdddb9e5397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Tue, 18 Feb 2020 20:41:58 +0100 Subject: [PATCH 14/47] Update README.md --- README.md | 82 +++++++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 41e51fa9..76358f59 100644 --- a/README.md +++ b/README.md @@ -86,62 +86,62 @@ Have a look [here](example/lib/main.dart) for a more detailed example. #### Connecting/Authenticating -| Function | Description| Android | iOS | -|---|---|---|--| -| connectToSpotifyRemote | Connects the App to Spotify | :heavy_check_mark: | :construction_worker: | -| getAuthenticationToken | Gets the Authentication Token that you can use to work with the [Web Api](https://developer.spotify.com/documentation/web-api/) |:heavy_check_mark: | :construction_worker: | -| logout | logs the user out and disconnects the app connection |:construction_worker: | :construction_worker: | +| Function | Description| Android | iOS | Web | +|---|---|---|---|---| +| connectToSpotifyRemote | Connects the App to Spotify | :heavy_check_mark: | :construction_worker: | :heavy_check_mark: | +| getAuthenticationToken | Gets the Authentication Token that you can use to work with the [Web Api](https://developer.spotify.com/documentation/web-api/) |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | +| logout | logs the user out and disconnects the app connection |:construction_worker: | :construction_worker: | :heavy_check_mark: | #### Player Api -| Function | Description| Android | iOS | -|---|---|---|--| -| getCrossfadeState | Gets the current crossfade state |:heavy_check_mark: | :construction_worker: | -| getPlayerState | Gets the current player state |:heavy_check_mark: | :construction_worker: | -| pause | Pauses the current track |:heavy_check_mark: | :construction_worker: | -| play | Plays the given spotifyUri |:heavy_check_mark: | :construction_worker: | -| queue | Queues given spotifyUri |:heavy_check_mark: | :construction_worker: | -| resume | Resumes the current track |:heavy_check_mark: | :construction_worker: | -| skipNext | Skips to next track | :heavy_check_mark: | :construction_worker: | -| skipPrevious | Skips to previous track |:heavy_check_mark: | :construction_worker: | -| seekTo | Seeks the current track to the given position in milliseconds | :heavy_check_mark: |:construction_worker: | -| seekToRelativePosition | Adds to the current position of the track the given milliseconds |:heavy_check_mark: | :construction_worker: | -| subscribeToPlayerContext | Subscribes to the current player context |:heavy_check_mark:|:construction_worker: | -| subscribeToPlayerState| Subscribes to the current player state |:heavy_check_mark: |:construction_worker:| -| getCrossfadeState | Gets the current crossfade state |:heavy_check_mark: | :construction_worker: | -| toggleShuffle | Cycles through the shuffle modes |:heavy_check_mark: | :construction_worker: | -| toggleRepeat | Cycles through the repeat modes | :heavy_check_mark: | :construction_worker: | +| Function | Description | Android | iOS | Web | +|---|---|---|---|---| +| getCrossfadeState | Gets the current crossfade state | :heavy_check_mark: | :construction_worker: | :x: | +| getPlayerState | Gets the current player state |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | +| pause | Pauses the current track |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | +| play | Plays the given spotifyUri |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | +| queue | Queues given spotifyUri |:heavy_check_mark: | :construction_worker: | :x: | +| resume | Resumes the current track |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | +| skipNext | Skips to next track | :heavy_check_mark: | :construction_worker: | :heavy_check_mark: | +| skipPrevious | Skips to previous track |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | +| seekTo | Seeks the current track to the given position in milliseconds | :heavy_check_mark: |:construction_worker: | :construction_worker: | +| seekToRelativePosition | Adds to the current position of the track the given milliseconds |:heavy_check_mark: | :construction_worker: | :construction_worker: | +| subscribeToPlayerContext | Subscribes to the current player context |:heavy_check_mark:|:construction_worker: | :heavy_check_mark: | +| subscribeToPlayerState| Subscribes to the current player state |:heavy_check_mark: |:construction_worker:| :heavy_check_mark: | +| getCrossfadeState | Gets the current crossfade state |:heavy_check_mark: | :construction_worker: | :x: | +| toggleShuffle | Cycles through the shuffle modes |:heavy_check_mark: | :construction_worker: | :construction_worker: | +| toggleRepeat | Cycles through the repeat modes | :heavy_check_mark: | :construction_worker: | :construction_worker: | #### Images Api -| Function | Description| Android | iOS | -|---|---|---|--| -| getImage | Get the image from the given spotifyUri |:construction_worker: | :construction_worker: | +| Function | Description| Android | iOS | Web | +|---|---|---|---|---| +| getImage | Get the image from the given spotifyUri |:construction_worker: | :construction_worker: | :construction_worker: | #### User Api -| Function | Description| Android | iOS | -|---|---|---|--| -| addToLibrary | Adds the given spotifyUri to the users library |:heavy_check_mark: | :construction_worker: | -| getCapabilities | Gets the current users capabilities |:heavy_check_mark: | :construction_worker: | -| getLibraryState | Gets the current library state |:heavy_check_mark: | :construction_worker: | -| removeFromLibrary | Removes the given spotifyUri to the users library |:heavy_check_mark: | :construction_worker: | -| subscribeToCapabilities | Subscribes to the current users capabilities |:heavy_check_mark: | :construction_worker: | -| subscribeToUserStatus | Subscrives to the current users status |:heavy_check_mark: | :construction_worker: | +| Function | Description| Android | iOS | Web | +|---|---|---|---|---| +| addToLibrary | Adds the given spotifyUri to the users library |:heavy_check_mark: | :construction_worker: | :construction_worker: | +| getCapabilities | Gets the current users capabilities |:heavy_check_mark: | :construction_worker: | :construction_worker: | +| getLibraryState | Gets the current library state |:heavy_check_mark: | :construction_worker: | :construction_worker: | +| removeFromLibrary | Removes the given spotifyUri to the users library |:heavy_check_mark: | :construction_worker: | :construction_worker: | +| subscribeToCapabilities | Subscribes to the current users capabilities |:heavy_check_mark: | :construction_worker: | :construction_worker: | +| subscribeToUserStatus | Subscrives to the current users status |:heavy_check_mark: | :construction_worker: | :construction_worker: | #### Connect Api -| Function | Description| Android | iOS | -|---|---|---|--| -| connectSwitchToLocalDevice | Switch to play music on this (local) device |:construction_worker: | :construction_worker: | +| Function | Description| Android | iOS | Web | +|---|---|---|---|---| +| connectSwitchToLocalDevice | Switch to play music on this (local) device |:construction_worker: | :construction_worker: | :construction_worker: | #### Content Api -| Function | Description| Android | iOS | -|---|---|---|--| -| getChildrenOfItem | tbd |:construction_worker: | :construction_worker: | -| getRecommendedContentItems | tbd |:construction_worker: | :construction_worker: | -| playContentItem | tbd |:construction_worker: | :construction_worker: | +| Function | Description| Android | iOS | Web | +|---|---|---|---|---| +| getChildrenOfItem | tbd |:construction_worker: | :construction_worker: | :construction_worker: | +| getRecommendedContentItems | tbd |:construction_worker: | :construction_worker: | :construction_worker: | +| playContentItem | tbd |:construction_worker: | :construction_worker: | :construction_worker: | ## Official Spotify Docs From 2c3fbd1abf6308436c45b34406b77c1872ccf483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Thu, 20 Feb 2020 15:24:24 +0100 Subject: [PATCH 15/47] Fixed compile errors in release mode. --- lib/spotify_sdk_web.dart | 133 +++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 68 deletions(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 1c57f064..313482ef 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -103,6 +103,13 @@ class SpotifySdkPlugin { final StreamController playerStateEventController; final StreamController playerCapabilitiesEventController; final StreamController userStateEventController; + + /// Dio http client + final Dio _dio = Dio( + BaseOptions( + baseUrl: 'https://api.spotify.com/v1/me/player', + ) + ); SpotifySdkPlugin(this.playerContextEventController, this.playerStateEventController, this.playerCapabilitiesEventController, this.userStateEventController) { _initializeSpotify(); @@ -187,7 +194,7 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: - await promiseToFuture(_currentPlayer?.play(call.arguments[PARAM_SPOTIFY_URI], await _getSpotifyAuthToken())); + await promiseToFuture(_play(call.arguments[PARAM_SPOTIFY_URI])); break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); @@ -379,6 +386,63 @@ class SpotifySdkPlugin { throw PlatformException(message: "Client id or redirectUrl are not set or have invalid format", code: "Authentication Error"); } } + /// Starts track playback on the device. + _play(String uri) async { + if(_currentPlayer?.deviceID == null) { + throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); + } + + await _dio.put('/play', + data: { 'uris': [uri] }, + queryParameters: { + 'device_id': _currentPlayer.deviceID + }, + options: Options( + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ${await _getSpotifyAuthToken()}' + }, + ), + ); + } + /// Toggles shuffle on the current player. + toggleShuffle(bool state) async { + if(_currentPlayer?.deviceID == null) { + throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); + } + + await _dio.put('https://api.spotify.com/v1/me/player/shuffle', + queryParameters: { + 'state': state, + 'device_id': _currentPlayer.deviceID + }, + options: Options( + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ${await _getSpotifyAuthToken()}' + }, + ), + ); + } + /// Toggles repeat on the current player. + toggleRepeat(bool state) async { + if(_currentPlayer?.deviceID == null) { + throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); + } + + await _dio.put('https://api.spotify.com/v1/me/player/repeat', + queryParameters: { + 'state': state, + 'device_id': _currentPlayer.deviceID + }, + options: Options( + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ${await _getSpotifyAuthToken()}' + }, + ), + ); + } /// Converts a native WebPlaybackState to the library PlayerState PlayerState toPlayerState(WebPlaybackState state) { if(state == null) return null; @@ -450,13 +514,6 @@ class SpotifySdkPlugin { /// Spotify Player Object @JS('Spotify.Player') class Player { - /// Dio http client - final Dio _dio = Dio( - BaseOptions( - baseUrl: 'https://api.spotify.com/v1/me/player', - ) - ); - /// Device id of the player. String deviceID; @@ -488,66 +545,6 @@ class Player { external dynamic previousTrack(); /// Skip to the next track in local playback. external dynamic nextTrack(); - /// Starts track playback on the device. - play(String uri, String accessToken) async { - if(deviceID == null) { - log('Spotify player not connected!'); - return; - } - - await _dio.put('/play', - data: { 'uris': [uri] }, - queryParameters: { - 'device_id': deviceID - }, - options: Options( - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $accessToken' - }, - ), - ); - } - /// Toggles shuffle on the current player. - toggleShuffle(bool state, String accessToken) async { - if(deviceID == null) { - log('Spotify player not connected!'); - return; - } - - await _dio.put('https://api.spotify.com/v1/me/player/shuffle', - queryParameters: { - 'state': state, - 'device_id': deviceID - }, - options: Options( - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $accessToken' - }, - ), - ); - } - /// Toggles repeat on the current player. - toggleRepeat(bool state, String accessToken) async { - if(deviceID == null) { - log('Spotify player not connected!'); - return; - } - - await _dio.put('https://api.spotify.com/v1/me/player/repeat', - queryParameters: { - 'state': state, - 'device_id': deviceID - }, - options: Options( - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $accessToken' - }, - ), - ); - } } @JS() @anonymous From 12b52ee7ff2889f6a82f78c176a9f7127c3088bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Thu, 20 Feb 2020 16:04:03 +0100 Subject: [PATCH 16/47] Update spotify_sdk_web.dart --- lib/spotify_sdk_web.dart | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 313482ef..d0fcb0d0 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -178,7 +178,19 @@ class SpotifySdkPlugin { ); _registerPlayerEvents(_currentPlayer); - return await promiseToFuture(_currentPlayer.connect()); + bool result = await promiseToFuture(_currentPlayer.connect()); + if(result == false) { + return false; + } else { + // wait for the ready event + while(_currentPlayer != null) { + if(_currentPlayer.deviceID?.isNotEmpty == true) { + return true; + } + await Future.delayed(Duration(milliseconds: 250)); + } + return false; + } break; case METHOD_GET_AUTHENTICATION_TOKEN: return await _getSpotifyAuthToken(clientId: call.arguments[PARAM_CLIENT_ID], redirectUrl: call.arguments[PARAM_REDIRECT_URL]); @@ -190,6 +202,7 @@ class SpotifySdkPlugin { } else { _unregisterPlayerEvents(_currentPlayer); _currentPlayer.disconnect(); + _currentPlayer = null; return true; } break; @@ -278,6 +291,7 @@ class SpotifySdkPlugin { allowInterop( (WebPlaybackError error) { log('initialization_error: ${error.message}'); + _currentPlayer = null; } ) ); @@ -286,6 +300,7 @@ class SpotifySdkPlugin { allowInterop( (WebPlaybackError error) { log('authentication_error: ${error.message}'); + _currentPlayer = null; } ) ); @@ -294,6 +309,7 @@ class SpotifySdkPlugin { allowInterop( (WebPlaybackError error) { log('account_error: ${error.message}'); + _currentPlayer = null; } ) ); From b3e2c24850f119d3eca944fdaeb943df88d9bf5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Fri, 21 Feb 2020 23:31:10 +0100 Subject: [PATCH 17/47] Update spotify_sdk_web.dart --- lib/spotify_sdk_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index d0fcb0d0..142be2cf 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -207,7 +207,7 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: - await promiseToFuture(_play(call.arguments[PARAM_SPOTIFY_URI])); + await _play(call.arguments[PARAM_SPOTIFY_URI]); break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); From aa74fce87fd862e543ef0654dd7e2ec9edbb0346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sat, 22 Feb 2020 00:21:24 +0100 Subject: [PATCH 18/47] Update spotify_sdk_web.dart --- lib/spotify_sdk_web.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 142be2cf..f4cf46c8 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -207,7 +207,7 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: - await _play(call.arguments[PARAM_SPOTIFY_URI]); + return await _play(call.arguments[PARAM_SPOTIFY_URI]); break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); @@ -403,7 +403,7 @@ class SpotifySdkPlugin { } } /// Starts track playback on the device. - _play(String uri) async { + Future _play(String uri) async { if(_currentPlayer?.deviceID == null) { throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); } @@ -422,7 +422,7 @@ class SpotifySdkPlugin { ); } /// Toggles shuffle on the current player. - toggleShuffle(bool state) async { + Future toggleShuffle(bool state) async { if(_currentPlayer?.deviceID == null) { throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); } @@ -441,7 +441,7 @@ class SpotifySdkPlugin { ); } /// Toggles repeat on the current player. - toggleRepeat(bool state) async { + Future toggleRepeat(bool state) async { if(_currentPlayer?.deviceID == null) { throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); } From 2d3fa755ec33d5d09cdf99762c6bc161954f6ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sat, 22 Feb 2020 01:43:01 +0100 Subject: [PATCH 19/47] test 1 --- lib/spotify_sdk_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index f4cf46c8..5aaea868 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -207,7 +207,7 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: - return await _play(call.arguments[PARAM_SPOTIFY_URI]); + _play(call.arguments[PARAM_SPOTIFY_URI]); break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); From 407eac2b3775658292879b2fd1cc7176f450aa84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sat, 22 Feb 2020 01:50:44 +0100 Subject: [PATCH 20/47] test 2 --- lib/spotify_sdk_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 5aaea868..fb62179a 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -207,7 +207,7 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: - _play(call.arguments[PARAM_SPOTIFY_URI]); + //_play(call.arguments[PARAM_SPOTIFY_URI]); break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); From 82d2b8a69d217db8fda44a44ec615d7a227383df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sat, 22 Feb 2020 02:10:03 +0100 Subject: [PATCH 21/47] Update spotify_sdk_web.dart --- lib/spotify_sdk_web.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index fb62179a..aac91215 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -207,7 +207,11 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: +<<<<<<< HEAD //_play(call.arguments[PARAM_SPOTIFY_URI]); +======= + return await _play(call.arguments[PARAM_SPOTIFY_URI]); +>>>>>>> parent of 2d3fa75... test 1 break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); From 7824c692cc570913ceac6c7ca35e50d7a59ca2e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sat, 22 Feb 2020 02:10:12 +0100 Subject: [PATCH 22/47] Revert "Update spotify_sdk_web.dart" This reverts commit 82d2b8a69d217db8fda44a44ec615d7a227383df. --- lib/spotify_sdk_web.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index aac91215..fb62179a 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -207,11 +207,7 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: -<<<<<<< HEAD //_play(call.arguments[PARAM_SPOTIFY_URI]); -======= - return await _play(call.arguments[PARAM_SPOTIFY_URI]); ->>>>>>> parent of 2d3fa75... test 1 break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); From cdecab64cdbadd185a9c662ec3e410971c04d734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sat, 22 Feb 2020 02:10:17 +0100 Subject: [PATCH 23/47] Revert "test 2" This reverts commit 407eac2b3775658292879b2fd1cc7176f450aa84. --- lib/spotify_sdk_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index fb62179a..5aaea868 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -207,7 +207,7 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: - //_play(call.arguments[PARAM_SPOTIFY_URI]); + _play(call.arguments[PARAM_SPOTIFY_URI]); break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); From 4e7324e476b18952c56a6d740934b830606f28a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sat, 22 Feb 2020 02:10:21 +0100 Subject: [PATCH 24/47] Revert "test 1" This reverts commit 2d3fa755ec33d5d09cdf99762c6bc161954f6ccd. --- lib/spotify_sdk_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 5aaea868..f4cf46c8 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -207,7 +207,7 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: - _play(call.arguments[PARAM_SPOTIFY_URI]); + return await _play(call.arguments[PARAM_SPOTIFY_URI]); break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); From 76ce4e7e93a46676d1f8b3507afa03abd9a03b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sat, 22 Feb 2020 02:11:43 +0100 Subject: [PATCH 25/47] Update spotify_sdk_web.dart --- lib/spotify_sdk_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index f4cf46c8..3a581647 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -207,7 +207,7 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: - return await _play(call.arguments[PARAM_SPOTIFY_URI]); + await _play(call.arguments[PARAM_SPOTIFY_URI]); break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); From 760ec55d3d434aeea486e1c9246fb8296504284f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Thu, 27 Feb 2020 22:04:04 +0100 Subject: [PATCH 26/47] Added queue method implementation. --- lib/spotify_sdk_web.dart | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 3a581647..97f725f8 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -209,6 +209,9 @@ class SpotifySdkPlugin { case METHOD_PLAY: await _play(call.arguments[PARAM_SPOTIFY_URI]); break; + case METHOD_QUEUE_TRACK: + await _queue(call.arguments[PARAM_SPOTIFY_URI]); + break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); break; @@ -421,6 +424,25 @@ class SpotifySdkPlugin { ), ); } + /// Adds a given track to the playback queue. + Future _queue(String uri) async { + if(_currentPlayer?.deviceID == null) { + throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); + } + + await _dio.put('/add-to-queue', + queryParameters: { + 'uri': uri, + 'device_id': _currentPlayer.deviceID + }, + options: Options( + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ${await _getSpotifyAuthToken()}' + }, + ), + ); + } /// Toggles shuffle on the current player. Future toggleShuffle(bool state) async { if(_currentPlayer?.deviceID == null) { From 9ada26727fd2d2aa54d5622d202c4830b959edc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Thu, 27 Feb 2020 22:04:25 +0100 Subject: [PATCH 27/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76358f59..22602da9 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Have a look [here](example/lib/main.dart) for a more detailed example. | getPlayerState | Gets the current player state |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | | pause | Pauses the current track |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | | play | Plays the given spotifyUri |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | -| queue | Queues given spotifyUri |:heavy_check_mark: | :construction_worker: | :x: | +| queue | Queues given spotifyUri |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | | resume | Resumes the current track |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | | skipNext | Skips to next track | :heavy_check_mark: | :construction_worker: | :heavy_check_mark: | | skipPrevious | Skips to previous track |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | From 1f62bdd16f07295fbd6a48098508419e814f06fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Thu, 27 Feb 2020 22:49:22 +0100 Subject: [PATCH 28/47] Update spotify_sdk_web.dart --- lib/spotify_sdk_web.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 97f725f8..cde892c2 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -430,7 +430,7 @@ class SpotifySdkPlugin { throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); } - await _dio.put('/add-to-queue', + await _dio.post('/add-to-queue', queryParameters: { 'uri': uri, 'device_id': _currentPlayer.deviceID From 533dfc5f2b887c472b5e08b41bda5a08d5bc41a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sun, 1 Mar 2020 23:23:28 +0100 Subject: [PATCH 29/47] Update SpotifySdkPlugin.kt --- .../main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt index f1169b6b..da3d9008 100644 --- a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt +++ b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt @@ -157,6 +157,8 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl .showAuthView(true) .build() + val replySubmitted = false + SpotifyAppRemote.connect(registrar.context(), connectionParams, object : ConnectionListener { override fun onConnected(spotifyAppRemoteValue: SpotifyAppRemote) { @@ -166,9 +168,11 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl capabilitiesChannel.setStreamHandler(CapabilitiesChannel(spotifyAppRemote!!.userApi)) userStatusChannel.setStreamHandler(UserStatusChannel(spotifyAppRemote!!.userApi)) result.success(true) + replySubmitted = true } override fun onFailure(throwable: Throwable) { + if(replySubmitted) return result.error(errorConnecting, "Something went wrong connecting spotify remote", throwable.message) } }) From ff68193cc12fb3da91c9dd213dc4dbbae7dcc4b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sun, 1 Mar 2020 23:30:49 +0100 Subject: [PATCH 30/47] Update SpotifySdkPlugin.kt --- .../main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt index da3d9008..3569d63d 100644 --- a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt +++ b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt @@ -157,7 +157,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl .showAuthView(true) .build() - val replySubmitted = false + Boolean replySubmitted = false SpotifyAppRemote.connect(registrar.context(), connectionParams, object : ConnectionListener { @@ -172,7 +172,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl } override fun onFailure(throwable: Throwable) { - if(replySubmitted) return + if(replySubmitted == true) return result.error(errorConnecting, "Something went wrong connecting spotify remote", throwable.message) } }) From f35bd7ad712075fa6ba8e88a1ae376a442cd9316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20R=C4=99bacz?= Date: Sun, 1 Mar 2020 23:55:27 +0100 Subject: [PATCH 31/47] Update SpotifySdkPlugin.kt --- .../main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt index 3569d63d..a4f2932c 100644 --- a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt +++ b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt @@ -157,7 +157,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl .showAuthView(true) .build() - Boolean replySubmitted = false + var replySubmitted = false SpotifyAppRemote.connect(registrar.context(), connectionParams, object : ConnectionListener { From 95de7185b9d8027794b620d761805bb6a40140bf Mon Sep 17 00:00:00 2001 From: Arnav Shrivastava Date: Thu, 11 Jun 2020 19:35:13 +0530 Subject: [PATCH 32/47] Changes to dynamically add scope in auth request. User needs to send an additional parameter "scope" as String - multiple scopes should be seperated with a comma "," without any spaces. This parameter is required along with clientId and redirectUrl. --- .gitignore | 1 + .../de/minimalme/spotify_sdk/SpotifySdkPlugin.kt | 15 ++++++--------- lib/spotify_sdk.dart | 5 +++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index fecfa4d0..7e621a8a 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ build/ .classpath .settings .env* +*.sqlite diff --git a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt index 6e4024f3..9a2c8a90 100644 --- a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt +++ b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt @@ -84,6 +84,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl private val paramClientId = "clientId" private val paramRedirectUrl = "redirectUrl" + private val paramScope = "scope" private val paramSpotifyUri = "spotifyUri" private val paramImageUri = "imageUri" private val paramImageDimension = "imageDimension" @@ -100,12 +101,6 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl private var connStatusEventChannel: SetEvent = event() private val requestCodeAuthentication = 1337 - private val scope = arrayOf( - "app-remote-control", - "user-modify-playback-state", - "playlist-read-private", - "playlist-modify-public", - "user-read-currently-playing") private var pendingOperation: PendingOperation? = null private var spotifyAppRemote: SpotifyAppRemote? = null @@ -128,7 +123,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl when (call.method) { //connecting to spotify methodConnectToSpotify -> connectToSpotify(call.argument(paramClientId), call.argument(paramRedirectUrl), result) - methodGetAuthenticationToken -> getAuthenticationToken(call.argument(paramClientId), call.argument(paramRedirectUrl), result) + methodGetAuthenticationToken -> getAuthenticationToken(call.argument(paramClientId), call.argument(paramRedirectUrl), call.argument(paramScope), result) methodLogoutFromSpotify -> logoutFromSpotify(result) //player api calls methodGetCrossfadeState -> spotifyPlayerApi?.getCrossfadeState() @@ -254,7 +249,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl } } - private fun getAuthenticationToken(clientId: String?, redirectUrl: String?, result: Result) { + private fun getAuthenticationToken(clientId: String?, redirectUrl: String?, scope = String?, result: Result) { if (registrar.activity() == null) { throw IllegalStateException("connectToSpotify needs a foreground activity") } @@ -262,10 +257,12 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl if (clientId.isNullOrBlank() || redirectUrl.isNullOrBlank()) { result.error(errorConnecting, "client id or redirectUrl are not set or have invalid format", "") } else { + //Convert Scope String 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(scope) + builder.setScopes(scopeArray) val request = builder.build() AuthorizationClient.openLoginActivity(registrar.activity(), requestCodeAuthentication, request) diff --git a/lib/spotify_sdk.dart b/lib/spotify_sdk.dart index 83ecc5ad..7c6c7d21 100644 --- a/lib/spotify_sdk.dart +++ b/lib/spotify_sdk.dart @@ -62,6 +62,7 @@ class SpotifySdk { // params static const String _paramClientId = "clientId"; static const String _paramRedirectUrl = "redirectUrl"; + static const String _paramScope = "scope" static const String _paramSpotifyUri = "spotifyUri"; static const String _paramImageUri = "imageUri"; static const String _paramImageDimension = "imageDimension"; @@ -93,11 +94,11 @@ class SpotifySdk { /// Throws a [PlatformException] if retrieving the authentication token failed. /// Throws a [MissingPluginException] if the method is not implemented on the native platforms. static Future getAuthenticationToken( - {@required String clientId, @required String redirectUrl}) async { + {@required String clientId, @required String redirectUrl, @required String scope}) async { try { final String authorization = await _channel.invokeMethod( _methodGetAuthenticationToken, - {_paramClientId: clientId, _paramRedirectUrl: redirectUrl}); + {_paramClientId: clientId, _paramRedirectUrl: redirectUrl, _paramScope: scope}); return authorization; } on Exception catch (e) { _logException(_methodGetAuthenticationToken, e); From 3e17533fb428e7f6e7d8038069c7ce7fcef04af6 Mon Sep 17 00:00:00 2001 From: Arnav Shrivastava Date: Thu, 11 Jun 2020 19:53:30 +0530 Subject: [PATCH 33/47] Changes to dynamically add scope in auth request Fixed issue in spotify_sdk.dart with missing ";" --- .gitignore | 1 + lib/spotify_sdk.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7e621a8a..bdf0c007 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ build/ .settings .env* *.sqlite +/.vs/spotify_sdk/v16/.suo diff --git a/lib/spotify_sdk.dart b/lib/spotify_sdk.dart index 7c6c7d21..582e6c12 100644 --- a/lib/spotify_sdk.dart +++ b/lib/spotify_sdk.dart @@ -62,7 +62,7 @@ class SpotifySdk { // params static const String _paramClientId = "clientId"; static const String _paramRedirectUrl = "redirectUrl"; - static const String _paramScope = "scope" + static const String _paramScope = "scope"; static const String _paramSpotifyUri = "spotifyUri"; static const String _paramImageUri = "imageUri"; static const String _paramImageDimension = "imageDimension"; From a6ad4698e15572e13ad4163d7a65b6cc0a6f71b0 Mon Sep 17 00:00:00 2001 From: Arnav Shrivastava Date: Thu, 11 Jun 2020 20:11:23 +0530 Subject: [PATCH 34/47] Revert "Changes to dynamically add scope in auth request" This reverts commit 3e17533fb428e7f6e7d8038069c7ce7fcef04af6. --- .gitignore | 1 - lib/spotify_sdk.dart | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index bdf0c007..7e621a8a 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,3 @@ build/ .settings .env* *.sqlite -/.vs/spotify_sdk/v16/.suo diff --git a/lib/spotify_sdk.dart b/lib/spotify_sdk.dart index 582e6c12..7c6c7d21 100644 --- a/lib/spotify_sdk.dart +++ b/lib/spotify_sdk.dart @@ -62,7 +62,7 @@ class SpotifySdk { // params static const String _paramClientId = "clientId"; static const String _paramRedirectUrl = "redirectUrl"; - static const String _paramScope = "scope"; + static const String _paramScope = "scope" static const String _paramSpotifyUri = "spotifyUri"; static const String _paramImageUri = "imageUri"; static const String _paramImageDimension = "imageDimension"; From d0ee77d71db9d554a1a0e8d298f143a00ae807f6 Mon Sep 17 00:00:00 2001 From: Arnav Shrivastava Date: Thu, 11 Jun 2020 20:16:25 +0530 Subject: [PATCH 35/47] Reverting to original for test --- .gitignore | 1 + .../minimalme/spotify_sdk/SpotifySdkPlugin.kt | 17 ++++++++++------- lib/spotify_sdk.dart | 7 +++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 7e621a8a..bdf0c007 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ build/ .settings .env* *.sqlite +/.vs/spotify_sdk/v16/.suo diff --git a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt index 9a2c8a90..d52ce1ff 100644 --- a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt +++ b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt @@ -84,7 +84,6 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl private val paramClientId = "clientId" private val paramRedirectUrl = "redirectUrl" - private val paramScope = "scope" private val paramSpotifyUri = "spotifyUri" private val paramImageUri = "imageUri" private val paramImageDimension = "imageDimension" @@ -101,6 +100,12 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl private var connStatusEventChannel: SetEvent = event() private val requestCodeAuthentication = 1337 + private val scope = arrayOf( + "app-remote-control", + "user-modify-playback-state", + "playlist-read-private", + "playlist-modify-public", + "user-read-currently-playing") private var pendingOperation: PendingOperation? = null private var spotifyAppRemote: SpotifyAppRemote? = null @@ -123,7 +128,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl when (call.method) { //connecting to spotify methodConnectToSpotify -> connectToSpotify(call.argument(paramClientId), call.argument(paramRedirectUrl), result) - methodGetAuthenticationToken -> getAuthenticationToken(call.argument(paramClientId), call.argument(paramRedirectUrl), call.argument(paramScope), result) + methodGetAuthenticationToken -> getAuthenticationToken(call.argument(paramClientId), call.argument(paramRedirectUrl), result) methodLogoutFromSpotify -> logoutFromSpotify(result) //player api calls methodGetCrossfadeState -> spotifyPlayerApi?.getCrossfadeState() @@ -249,7 +254,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl } } - private fun getAuthenticationToken(clientId: String?, redirectUrl: String?, scope = String?, result: Result) { + private fun getAuthenticationToken(clientId: String?, redirectUrl: String?, result: Result) { if (registrar.activity() == null) { throw IllegalStateException("connectToSpotify needs a foreground activity") } @@ -257,12 +262,10 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl if (clientId.isNullOrBlank() || redirectUrl.isNullOrBlank()) { result.error(errorConnecting, "client id or redirectUrl are not set or have invalid format", "") } else { - //Convert Scope String 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) + builder.setScopes(scope) val request = builder.build() AuthorizationClient.openLoginActivity(registrar.activity(), requestCodeAuthentication, request) @@ -323,4 +326,4 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl } -private class PendingOperation internal constructor(val method: String, val result: Result) +private class PendingOperation internal constructor(val method: String, val result: Result) \ No newline at end of file diff --git a/lib/spotify_sdk.dart b/lib/spotify_sdk.dart index 7c6c7d21..6aa0b4a2 100644 --- a/lib/spotify_sdk.dart +++ b/lib/spotify_sdk.dart @@ -62,7 +62,6 @@ class SpotifySdk { // params static const String _paramClientId = "clientId"; static const String _paramRedirectUrl = "redirectUrl"; - static const String _paramScope = "scope" static const String _paramSpotifyUri = "spotifyUri"; static const String _paramImageUri = "imageUri"; static const String _paramImageDimension = "imageDimension"; @@ -94,11 +93,11 @@ class SpotifySdk { /// Throws a [PlatformException] if retrieving the authentication token failed. /// Throws a [MissingPluginException] if the method is not implemented on the native platforms. static Future getAuthenticationToken( - {@required String clientId, @required String redirectUrl, @required String scope}) async { + {@required String clientId, @required String redirectUrl}) async { try { final String authorization = await _channel.invokeMethod( _methodGetAuthenticationToken, - {_paramClientId: clientId, _paramRedirectUrl: redirectUrl, _paramScope: scope}); + {_paramClientId: clientId, _paramRedirectUrl: redirectUrl}); return authorization; } on Exception catch (e) { _logException(_methodGetAuthenticationToken, e); @@ -494,4 +493,4 @@ extension ImageDimensionExtension on ImageDimension { }; int get value => values[this]; -} +} \ No newline at end of file From ad1abd042372e5746b0bb723058c8b5336557762 Mon Sep 17 00:00:00 2001 From: Arnav Shrivastava Date: Thu, 11 Jun 2020 20:23:59 +0530 Subject: [PATCH 36/47] Revert "Reverting to original for test" This reverts commit d0ee77d71db9d554a1a0e8d298f143a00ae807f6. --- .gitignore | 1 - .../minimalme/spotify_sdk/SpotifySdkPlugin.kt | 17 +++++++---------- lib/spotify_sdk.dart | 7 ++++--- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index bdf0c007..7e621a8a 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,3 @@ build/ .settings .env* *.sqlite -/.vs/spotify_sdk/v16/.suo diff --git a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt index d52ce1ff..9a2c8a90 100644 --- a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt +++ b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt @@ -84,6 +84,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl private val paramClientId = "clientId" private val paramRedirectUrl = "redirectUrl" + private val paramScope = "scope" private val paramSpotifyUri = "spotifyUri" private val paramImageUri = "imageUri" private val paramImageDimension = "imageDimension" @@ -100,12 +101,6 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl private var connStatusEventChannel: SetEvent = event() private val requestCodeAuthentication = 1337 - private val scope = arrayOf( - "app-remote-control", - "user-modify-playback-state", - "playlist-read-private", - "playlist-modify-public", - "user-read-currently-playing") private var pendingOperation: PendingOperation? = null private var spotifyAppRemote: SpotifyAppRemote? = null @@ -128,7 +123,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl when (call.method) { //connecting to spotify methodConnectToSpotify -> connectToSpotify(call.argument(paramClientId), call.argument(paramRedirectUrl), result) - methodGetAuthenticationToken -> getAuthenticationToken(call.argument(paramClientId), call.argument(paramRedirectUrl), result) + methodGetAuthenticationToken -> getAuthenticationToken(call.argument(paramClientId), call.argument(paramRedirectUrl), call.argument(paramScope), result) methodLogoutFromSpotify -> logoutFromSpotify(result) //player api calls methodGetCrossfadeState -> spotifyPlayerApi?.getCrossfadeState() @@ -254,7 +249,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl } } - private fun getAuthenticationToken(clientId: String?, redirectUrl: String?, result: Result) { + private fun getAuthenticationToken(clientId: String?, redirectUrl: String?, scope = String?, result: Result) { if (registrar.activity() == null) { throw IllegalStateException("connectToSpotify needs a foreground activity") } @@ -262,10 +257,12 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl if (clientId.isNullOrBlank() || redirectUrl.isNullOrBlank()) { result.error(errorConnecting, "client id or redirectUrl are not set or have invalid format", "") } else { + //Convert Scope String 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(scope) + builder.setScopes(scopeArray) val request = builder.build() AuthorizationClient.openLoginActivity(registrar.activity(), requestCodeAuthentication, request) @@ -326,4 +323,4 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl } -private class PendingOperation internal constructor(val method: String, val result: Result) \ No newline at end of file +private class PendingOperation internal constructor(val method: String, val result: Result) diff --git a/lib/spotify_sdk.dart b/lib/spotify_sdk.dart index 6aa0b4a2..7c6c7d21 100644 --- a/lib/spotify_sdk.dart +++ b/lib/spotify_sdk.dart @@ -62,6 +62,7 @@ class SpotifySdk { // params static const String _paramClientId = "clientId"; static const String _paramRedirectUrl = "redirectUrl"; + static const String _paramScope = "scope" static const String _paramSpotifyUri = "spotifyUri"; static const String _paramImageUri = "imageUri"; static const String _paramImageDimension = "imageDimension"; @@ -93,11 +94,11 @@ class SpotifySdk { /// Throws a [PlatformException] if retrieving the authentication token failed. /// Throws a [MissingPluginException] if the method is not implemented on the native platforms. static Future getAuthenticationToken( - {@required String clientId, @required String redirectUrl}) async { + {@required String clientId, @required String redirectUrl, @required String scope}) async { try { final String authorization = await _channel.invokeMethod( _methodGetAuthenticationToken, - {_paramClientId: clientId, _paramRedirectUrl: redirectUrl}); + {_paramClientId: clientId, _paramRedirectUrl: redirectUrl, _paramScope: scope}); return authorization; } on Exception catch (e) { _logException(_methodGetAuthenticationToken, e); @@ -493,4 +494,4 @@ extension ImageDimensionExtension on ImageDimension { }; int get value => values[this]; -} \ No newline at end of file +} From 9b0e48e14ef73dca1d31f71faed097e99476b83f Mon Sep 17 00:00:00 2001 From: Arnav Shrivastava Date: Thu, 11 Jun 2020 20:27:25 +0530 Subject: [PATCH 37/47] Changes to dynamically add scope in auth request --- .gitignore | 1 + lib/spotify_sdk.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7e621a8a..bdf0c007 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ build/ .settings .env* *.sqlite +/.vs/spotify_sdk/v16/.suo diff --git a/lib/spotify_sdk.dart b/lib/spotify_sdk.dart index 7c6c7d21..582e6c12 100644 --- a/lib/spotify_sdk.dart +++ b/lib/spotify_sdk.dart @@ -62,7 +62,7 @@ class SpotifySdk { // params static const String _paramClientId = "clientId"; static const String _paramRedirectUrl = "redirectUrl"; - static const String _paramScope = "scope" + static const String _paramScope = "scope"; static const String _paramSpotifyUri = "spotifyUri"; static const String _paramImageUri = "imageUri"; static const String _paramImageDimension = "imageDimension"; From e4b69c1040e636ecfb886cc02e805f198be375bb Mon Sep 17 00:00:00 2001 From: Arnav Shrivastava Date: Thu, 11 Jun 2020 20:31:26 +0530 Subject: [PATCH 38/47] Changes to dynamically add scope in auth request --- .../main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt index 9a2c8a90..be9d09b7 100644 --- a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt +++ b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt @@ -249,7 +249,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl } } - private fun getAuthenticationToken(clientId: String?, redirectUrl: String?, scope = String?, result: Result) { + private fun getAuthenticationToken(clientId: String?, redirectUrl: String?, scope: String?, result: Result) { if (registrar.activity() == null) { throw IllegalStateException("connectToSpotify needs a foreground activity") } From bc60b3900935da0dbad0828196de24139beb5ccb Mon Sep 17 00:00:00 2001 From: Arnav Shrivastava Date: Fri, 12 Jun 2020 12:30:59 +0530 Subject: [PATCH 39/47] Changes to dynamically add scope in auth request --- README.md | 5 +++-- .../main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 164e7e17..77ba225f 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,11 @@ To start using this package you first have to connect to Spotify. To only connec await SpotifySdk.connectToSpotifyRemote(clientId: "", redirectUrl: "") ``` -If you want to use the web api aswell you have to use this method to get the authentication token. +If you want to use the web api aswell you have to use this method to get the authentication token. +You can specify multiple scopes by seperating them with a comma "," as shown below. For more information on scopes you can refer [Spotify Authorization Scopes Guide](https://developer.spotify.com/documentation/general/guides/scopes/) ```dart -var authenticationToken = await SpotifySdk.getAuthenticationToken(clientId: "", redirectUrl: ""); +var authenticationToken = await SpotifySdk.getAuthenticationToken(clientId: "", redirectUrl: "", scope: "app-remote-control,user-modify-playback-state,playlist-read-private"); ``` tbd... diff --git a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt index be9d09b7..5e62480e 100644 --- a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt +++ b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt @@ -257,8 +257,8 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl if (clientId.isNullOrBlank() || redirectUrl.isNullOrBlank()) { result.error(errorConnecting, "client id or redirectUrl are not set or have invalid format", "") } else { - //Convert Scope String to Array. Delimiter set as comma "," - val scopeArray = scope.split(",").toTypedArray() + //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) From 21820b0d94704f159b8fb7c09d1bc8997a23e913 Mon Sep 17 00:00:00 2001 From: Tobias Busch Date: Tue, 7 Jul 2020 07:41:10 +0200 Subject: [PATCH 40/47] Upgrades package versions and fixes merge foo --- example/pubspec.yaml | 4 +- lib/spotify_sdk_web.dart | 586 +++++++++++++++++++++------------------ pubspec.yaml | 10 +- 3 files changed, 326 insertions(+), 274 deletions(-) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index a82b9111..443f4b10 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -3,12 +3,12 @@ description: Demonstrates how to use the spotify_sdk plugin. publish_to: 'none' environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.7.0 <3.0.0" dependencies: flutter: sdk: flutter - logger: ^0.8.0 + logger: ^0.9.1 flutter_dotenv: ^2.1.0 # The following adds the Cupertino Icons font to your application. diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index cde892c2..387e04c5 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -4,13 +4,12 @@ library spotify_sdk; import 'dart:async'; import 'dart:convert'; import 'dart:developer'; -import 'dart:js'; import 'dart:html'; +import 'dart:js'; import 'package:dio/dio.dart'; import 'package:flutter/services.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - import 'package:js/js.dart'; import 'package:js/js_util.dart'; import 'package:spotify_sdk/models/album.dart'; @@ -25,14 +24,17 @@ import 'package:spotify_sdk/models/track.dart'; class SpotifySdkPlugin { // event channels static const String CHANNEL_NAME = "spotify_sdk"; - static const String PLAYER_CONTEXT_SUBSCRIPTION = "player_context_subscription"; + static const String PLAYER_CONTEXT_SUBSCRIPTION = + "player_context_subscription"; static const String PLAYER_STATE_SUBSCRIPTION = "player_state_subscription"; - static const String PLAYER_CAPABILITIES_SUBSCRIPTION = "capabilities_subscription"; + static const String PLAYER_CAPABILITIES_SUBSCRIPTION = + "capabilities_subscription"; static const String USER_STATUS_SUBSCRIPTION = "user_status_subscription"; // connecting static const String METHOD_CONNECT_TO_SPOTIFY = "connectToSpotify"; - static const String METHOD_GET_AUTHENTICATION_TOKEN = "getAuthenticationToken"; + static const String METHOD_GET_AUTHENTICATION_TOKEN = + "getAuthenticationToken"; static const String METHOD_LOGOUT_FROM_SPOTIFY = "logoutFromSpotify"; // player api @@ -42,8 +44,10 @@ class SpotifySdkPlugin { static const String METHOD_PAUSE = "pause"; static const String METHOD_QUEUE_TRACK = "queueTrack"; static const String METHOD_RESUME = "resume"; - static const String METHOD_SEEK_TO_RELATIVE_POSITION = "seekToRelativePosition"; - static const String METHOD_SET_PODCAST_PLAYBACK_SPEED = "setPodcastPlaybackSpeed"; + static const String METHOD_SEEK_TO_RELATIVE_POSITION = + "seekToRelativePosition"; + static const String METHOD_SET_PODCAST_PLAYBACK_SPEED = + "setPodcastPlaybackSpeed"; static const String METHOD_SKIP_NEXT = "skipNext"; static const String METHOD_SKIP_PREVIOUS = "skipPrevious"; static const String METHOD_SKIP_TO_INDEX = "skipToIndex"; @@ -52,13 +56,13 @@ class SpotifySdkPlugin { static const String METHOD_TOGGLE_SHUFFLE = "toggleShuffle"; // user api - static const METHOD_ADD_TO_LIBRARY = "addToLibrary"; - static const METHOD_REMOVE_FROM_LIBRARY = "removeFromLibrary"; - static const METHOD_GET_CAPABILITIES = "getCapabilities"; - static const METHOD_GET_LIBRARY_STATE = "getLibraryState"; + static const String METHOD_ADD_TO_LIBRARY = "addToLibrary"; + static const String METHOD_REMOVE_FROM_LIBRARY = "removeFromLibrary"; + static const String METHOD_GET_CAPABILITIES = "getCapabilities"; + static const String METHOD_GET_LIBRARY_STATE = "getLibraryState"; //images api - static const METHOD_GET_IMAGE = "getImage"; + static const String METHOD_GET_IMAGE = "getImage"; static const String PARAM_CLIENT_ID = "clientId"; static const String PARAM_REDIRECT_URL = "redirectUrl"; @@ -73,7 +77,8 @@ class SpotifySdkPlugin { static const String ERROR_CONNECTING = "errorConnecting"; static const String ERROR_DISCONNECTING = "errorDisconnecting"; - static const String ERROR_AUTHENTICATION_TOKEN_ERROR = "authenticationTokenError"; + static const String ERROR_AUTHENTICATION_TOKEN_ERROR = + "authenticationTokenError"; // spotify sdk url static const String SPOTIFY_SDK_URL = 'https://sdk.scdn.co/spotify-player.js'; @@ -89,12 +94,16 @@ class SpotifySdkPlugin { /// Whether the Spotify SDK was already loaded. bool _sdkLoaded = false; + /// Current Spotify SDK player instance. Player _currentPlayer; + /// Current Spotify auth token. SpotifyToken _spotifyToken; + /// Cached client id used when connecting to Spotify. String cachedClientId; + /// Cached redirect url used when connecting to Spotify. String cachedRedirectUrl; @@ -103,61 +112,80 @@ class SpotifySdkPlugin { final StreamController playerStateEventController; final StreamController playerCapabilitiesEventController; final StreamController userStateEventController; - - /// Dio http client - final Dio _dio = Dio( - BaseOptions( - baseUrl: 'https://api.spotify.com/v1/me/player', - ) - ); - SpotifySdkPlugin(this.playerContextEventController, this.playerStateEventController, this.playerCapabilitiesEventController, this.userStateEventController) { + /// Dio http client + final Dio _dio = Dio(BaseOptions( + baseUrl: 'https://api.spotify.com/v1/me/player', + )); + + SpotifySdkPlugin( + this.playerContextEventController, + this.playerStateEventController, + this.playerCapabilitiesEventController, + this.userStateEventController) { _initializeSpotify(); } static void registerWith(Registrar registrar) { // method channel - final MethodChannel channel = MethodChannel(CHANNEL_NAME, const StandardMethodCodec(), registrar.messenger); + final MethodChannel channel = MethodChannel( + CHANNEL_NAME, const StandardMethodCodec(), registrar.messenger); // event channels - final PluginEventChannel playerContextEventChannel = PluginEventChannel(PLAYER_CONTEXT_SUBSCRIPTION); - final StreamController playerContextEventController = StreamController.broadcast(); + final PluginEventChannel playerContextEventChannel = + PluginEventChannel(PLAYER_CONTEXT_SUBSCRIPTION); + final StreamController playerContextEventController = + StreamController.broadcast(); playerContextEventChannel.controller = playerContextEventController; - final PluginEventChannel playerStateEventChannel = PluginEventChannel(PLAYER_STATE_SUBSCRIPTION); - final StreamController playerStateEventController = StreamController.broadcast(); + final PluginEventChannel playerStateEventChannel = + PluginEventChannel(PLAYER_STATE_SUBSCRIPTION); + final StreamController playerStateEventController = + StreamController.broadcast(); playerStateEventChannel.controller = playerStateEventController; - final PluginEventChannel playerCapabilitiesEventChannel = PluginEventChannel(PLAYER_CAPABILITIES_SUBSCRIPTION); - final StreamController playerCapabilitiesEventController = StreamController.broadcast(); - playerCapabilitiesEventChannel.controller = playerCapabilitiesEventController; - final PluginEventChannel userStatusEventChannel = PluginEventChannel(USER_STATUS_SUBSCRIPTION); - final StreamController userStatusEventController = StreamController.broadcast(); + final PluginEventChannel playerCapabilitiesEventChannel = + PluginEventChannel(PLAYER_CAPABILITIES_SUBSCRIPTION); + final StreamController playerCapabilitiesEventController = + StreamController.broadcast(); + playerCapabilitiesEventChannel.controller = + playerCapabilitiesEventController; + final PluginEventChannel userStatusEventChannel = + PluginEventChannel(USER_STATUS_SUBSCRIPTION); + final StreamController userStatusEventController = + StreamController.broadcast(); userStatusEventChannel.controller = userStatusEventController; - final SpotifySdkPlugin instance = SpotifySdkPlugin(playerContextEventController, playerStateEventController, playerCapabilitiesEventController, userStatusEventController); + final SpotifySdkPlugin instance = SpotifySdkPlugin( + playerContextEventController, + playerStateEventController, + playerCapabilitiesEventController, + userStatusEventController); channel.setMethodCallHandler(instance.handleMethodCall); } Future handleMethodCall(MethodCall call) async { // check if spotify is loaded - if(_sdkLoaded == false) { + if (_sdkLoaded == false) { throw PlatformException( - code: 'Uninitialized', - details: "The Spotify SDK wasn't initialized yet" - ); + code: 'Uninitialized', + details: "The Spotify SDK wasn't initialized yet"); } switch (call.method) { case METHOD_CONNECT_TO_SPOTIFY: log('Connecting to Spotify...'); - if(_currentPlayer != null) { + if (_currentPlayer != null) { return true; } // update the client id and redirect url String clientId = call.arguments[PARAM_CLIENT_ID]; String redirectUrl = call.arguments[PARAM_REDIRECT_URL]; String playerName = call.arguments[PARAM_PLAYER_NAME]; - if (!(clientId?.isNotEmpty == true && redirectUrl?.isNotEmpty == true)) { - throw PlatformException(message: "Client id or redirectUrl are not set or have invalid format", code: "Authentication Error"); + if (!(clientId?.isNotEmpty == true && + redirectUrl?.isNotEmpty == true)) { + throw PlatformException( + message: + "Client id or redirectUrl are not set or have invalid format", + code: "Authentication Error"); } cachedClientId = clientId; cachedRedirectUrl = redirectUrl; @@ -166,38 +194,37 @@ class SpotifySdkPlugin { await _getSpotifyAuthToken(); // create player - _currentPlayer = Player( - PlayerOptions( + _currentPlayer = Player(PlayerOptions( name: playerName, getOAuthToken: allowInterop((Function callback, t) { _getSpotifyAuthToken().then((value) { callback(value); }); - }) - ) - ); - + }))); + _registerPlayerEvents(_currentPlayer); bool result = await promiseToFuture(_currentPlayer.connect()); - if(result == false) { + if (result == false) { return false; } else { // wait for the ready event - while(_currentPlayer != null) { - if(_currentPlayer.deviceID?.isNotEmpty == true) { + while (_currentPlayer != null) { + if (_currentPlayer.deviceID?.isNotEmpty == true) { return true; } await Future.delayed(Duration(milliseconds: 250)); } return false; } - break; + break; case METHOD_GET_AUTHENTICATION_TOKEN: - return await _getSpotifyAuthToken(clientId: call.arguments[PARAM_CLIENT_ID], redirectUrl: call.arguments[PARAM_REDIRECT_URL]); - break; + return await _getSpotifyAuthToken( + clientId: call.arguments[PARAM_CLIENT_ID], + redirectUrl: call.arguments[PARAM_REDIRECT_URL]); + break; case METHOD_LOGOUT_FROM_SPOTIFY: log('Disconnecting from Spotify...'); - if(_currentPlayer == null) { + if (_currentPlayer == null) { return false; } else { _unregisterPlayerEvents(_currentPlayer); @@ -205,25 +232,25 @@ class SpotifySdkPlugin { _currentPlayer = null; return true; } - break; + break; case METHOD_PLAY: await _play(call.arguments[PARAM_SPOTIFY_URI]); - break; + break; case METHOD_QUEUE_TRACK: await _queue(call.arguments[PARAM_SPOTIFY_URI]); - break; + break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); - break; + break; case METHOD_PAUSE: await promiseToFuture(_currentPlayer?.pause()); - break; + break; case METHOD_SKIP_NEXT: await promiseToFuture(_currentPlayer?.nextTrack()); - break; + break; case METHOD_SKIP_PREVIOUS: await promiseToFuture(_currentPlayer?.previousTrack()); - break; + break; /*case METHOD_TOGGLE_SHUFFLE: //TODO: Needs a state parameter (true/false) //await _currentPlayer?.toggleShuffle(state, await _getSpotifyAuthToken()); @@ -233,189 +260,164 @@ class SpotifySdkPlugin { //await _currentPlayer?.toggleRepeat(state, await _getSpotifyAuthToken()); break;*/ case METHOD_GET_PLAYER_STATE: - WebPlaybackState stateRaw = await promiseToFuture(_currentPlayer?.getCurrentState()); - if(stateRaw == null) return null; + WebPlaybackState stateRaw = + await promiseToFuture(_currentPlayer?.getCurrentState()); + if (stateRaw == null) return null; return jsonEncode(toPlayerState(stateRaw).toJson()); - break; + break; default: throw PlatformException( - code: 'Unimplemented', - details: "The spotify_sdk plugin for web doesn't implement the method '${call.method}'"); + code: 'Unimplemented', + details: + "The spotify_sdk plugin for web doesn't implement the method '${call.method}'"); } } /// Loads the Spotify SDK library. _initializeSpotify() { - context['onSpotifyWebPlaybackSDKReady'] = allowInterop(_onSpotifyInitialized); - querySelector('body').children.add(ScriptElement()..src=SPOTIFY_SDK_URL); + context['onSpotifyWebPlaybackSDKReady'] = + allowInterop(_onSpotifyInitialized); + querySelector('body').children.add(ScriptElement()..src = SPOTIFY_SDK_URL); } + /// Called when the Spotify library is loaded. _onSpotifyInitialized() { log('Spotify Initialized!'); _sdkLoaded = true; } + /// Registers Spotify event handlers. _registerPlayerEvents(Player player) { // player state - player.addListener( - 'player_state_changed', - allowInterop( - (WebPlaybackState state) { - if(state == null) return; - playerStateEventController.add(jsonEncode(toPlayerState(state).toJson())); - playerContextEventController.add(jsonEncode(toPlayerContext(state).toJson())); - } - ) - ); + player.addListener('player_state_changed', + allowInterop((WebPlaybackState state) { + if (state == null) return; + playerStateEventController.add(jsonEncode(toPlayerState(state).toJson())); + playerContextEventController + .add(jsonEncode(toPlayerContext(state).toJson())); + })); // ready/not ready - player.addListener( - 'ready', - allowInterop( - (WebPlaybackPlayer player) { - log('Device ready! ${player?.device_id}'); - _currentPlayer.deviceID = player.device_id; - } - ) - ); - player.addListener( - 'not_ready', - allowInterop( - (event) { - log('Device not ready!'); - _currentPlayer.deviceID = null; - } - ) - ); + player.addListener('ready', allowInterop((WebPlaybackPlayer player) { + log('Device ready! ${player?.device_id}'); + _currentPlayer.deviceID = player.device_id; + })); + player.addListener('not_ready', allowInterop((event) { + log('Device not ready!'); + _currentPlayer.deviceID = null; + })); // errors - player.addListener( - 'initialization_error', - allowInterop( - (WebPlaybackError error) { - log('initialization_error: ${error.message}'); - _currentPlayer = null; - } - ) - ); - player.addListener( - 'authentication_error', - allowInterop( - (WebPlaybackError error) { - log('authentication_error: ${error.message}'); - _currentPlayer = null; - } - ) - ); - player.addListener( - 'account_error', - allowInterop( - (WebPlaybackError error) { - log('account_error: ${error.message}'); - _currentPlayer = null; - } - ) - ); - player.addListener( - 'playback_error', - allowInterop( - (WebPlaybackError error) { - log('playback_error: ${error.message}'); - } - ) - ); + player.addListener('initialization_error', + allowInterop((WebPlaybackError error) { + log('initialization_error: ${error.message}'); + _currentPlayer = null; + })); + player.addListener('authentication_error', + allowInterop((WebPlaybackError error) { + log('authentication_error: ${error.message}'); + _currentPlayer = null; + })); + player.addListener('account_error', allowInterop((WebPlaybackError error) { + log('account_error: ${error.message}'); + _currentPlayer = null; + })); + player.addListener('playback_error', allowInterop((WebPlaybackError error) { + log('playback_error: ${error.message}'); + })); } + _unregisterPlayerEvents(Player player) { - player.removeListener( - 'player_state_changed' - ); - player.removeListener( - 'ready' - ); - player.removeListener( - 'not_ready' - ); - player.removeListener( - 'initialization_error' - ); - player.removeListener( - 'authentication_error' - ); - player.removeListener( - 'account_error' - ); - player.removeListener( - 'playback_error' - ); + player.removeListener('player_state_changed'); + player.removeListener('ready'); + player.removeListener('not_ready'); + player.removeListener('initialization_error'); + player.removeListener('authentication_error'); + player.removeListener('account_error'); + player.removeListener('playback_error'); } + /// Gets the current Spotify token or reauthenticates the user if the token expired. - Future _getSpotifyAuthToken({String clientId, String redirectUrl}) async { - if(_spotifyToken != null && _spotifyToken.expiry > DateTime.now().millisecondsSinceEpoch) { + Future _getSpotifyAuthToken( + {String clientId, String redirectUrl}) async { + if (_spotifyToken != null && + _spotifyToken.expiry > DateTime.now().millisecondsSinceEpoch) { return _spotifyToken.token; } - if(clientId == null) { + if (clientId == null) { clientId = cachedClientId; } - if(redirectUrl == null) { + if (redirectUrl == null) { redirectUrl = cachedRedirectUrl; } String newToken = await _authenticateSpotify(clientId, redirectUrl); - _spotifyToken = SpotifyToken(newToken, DateTime.now().millisecondsSinceEpoch + 3600000); + _spotifyToken = + SpotifyToken(newToken, DateTime.now().millisecondsSinceEpoch + 3600000); return _spotifyToken.token; } + /// Authenticates the user and returns the access token on success. - Future _authenticateSpotify(String clientId, String redirectUrl) async { + Future _authenticateSpotify( + String clientId, String redirectUrl) async { if (clientId?.isNotEmpty == true && redirectUrl?.isNotEmpty == true) { - String scopes = AUTHENTICATION_SCOPES.join(' '); - String authUrl = 'https://accounts.spotify.com/authorize?client_id=$clientId&response_type=token&scope=$scopes&redirect_uri=$redirectUrl'; - - WindowBase authPopup = window.open(authUrl, "Spotify Authorization"); - String hash; - String error; - var sub = window.onMessage.listen(allowInterop((event) { - String message = event.data.toString(); - if(message.startsWith('#')) { - log('Hash received: ${event.data}'); - hash = message; - } else if (message.startsWith('?')) { - log('Authorization error: ${event.data}'); - error = message; - } - })); - - // loop and wait for auth - while(authPopup.closed == false && hash == null && error == null) { - // await response from the window - await Future.delayed(Duration(milliseconds: 250)); + String scopes = AUTHENTICATION_SCOPES.join(' '); + String authUrl = + 'https://accounts.spotify.com/authorize?client_id=$clientId&response_type=token&scope=$scopes&redirect_uri=$redirectUrl'; + + WindowBase authPopup = window.open(authUrl, "Spotify Authorization"); + String hash; + String error; + var sub = window.onMessage.listen(allowInterop((event) { + String message = event.data.toString(); + if (message.startsWith('#')) { + log('Hash received: ${event.data}'); + hash = message; + } else if (message.startsWith('?')) { + log('Authorization error: ${event.data}'); + error = message; } + })); - // cleanup - if(authPopup.closed == false) { - authPopup.close(); - } - await sub.cancel(); + // loop and wait for auth + while (authPopup.closed == false && hash == null && error == null) { + // await response from the window + await Future.delayed(Duration(milliseconds: 250)); + } - // check output - if(error != null || hash == null) { - throw PlatformException(message: "$error", code: "Authentication Error"); - } - return hash.split('&')[0].split('=')[1]; - } else { - throw PlatformException(message: "Client id or redirectUrl are not set or have invalid format", code: "Authentication Error"); + // cleanup + if (authPopup.closed == false) { + authPopup.close(); } + await sub.cancel(); + + // check output + if (error != null || hash == null) { + throw PlatformException( + message: "$error", code: "Authentication Error"); + } + return hash.split('&')[0].split('=')[1]; + } else { + throw PlatformException( + message: + "Client id or redirectUrl are not set or have invalid format", + code: "Authentication Error"); + } } + /// Starts track playback on the device. Future _play(String uri) async { - if(_currentPlayer?.deviceID == null) { - throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); + if (_currentPlayer?.deviceID == null) { + throw PlatformException( + message: "Spotify player not connected!", code: "Playback Error"); } - await _dio.put('/play', - data: { 'uris': [uri] }, - queryParameters: { - 'device_id': _currentPlayer.deviceID + await _dio.put( + '/play', + data: { + 'uris': [uri] }, + queryParameters: {'device_id': _currentPlayer.deviceID}, options: Options( headers: { 'Content-Type': 'application/json', @@ -424,17 +426,17 @@ class SpotifySdkPlugin { ), ); } + /// Adds a given track to the playback queue. Future _queue(String uri) async { - if(_currentPlayer?.deviceID == null) { - throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); + if (_currentPlayer?.deviceID == null) { + throw PlatformException( + message: "Spotify player not connected!", code: "Playback Error"); } - await _dio.post('/add-to-queue', - queryParameters: { - 'uri': uri, - 'device_id': _currentPlayer.deviceID - }, + await _dio.post( + '/add-to-queue', + queryParameters: {'uri': uri, 'device_id': _currentPlayer.deviceID}, options: Options( headers: { 'Content-Type': 'application/json', @@ -443,17 +445,17 @@ class SpotifySdkPlugin { ), ); } + /// Toggles shuffle on the current player. Future toggleShuffle(bool state) async { - if(_currentPlayer?.deviceID == null) { - throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); + if (_currentPlayer?.deviceID == null) { + throw PlatformException( + message: "Spotify player not connected!", code: "Playback Error"); } - await _dio.put('https://api.spotify.com/v1/me/player/shuffle', - queryParameters: { - 'state': state, - 'device_id': _currentPlayer.deviceID - }, + await _dio.put( + 'https://api.spotify.com/v1/me/player/shuffle', + queryParameters: {'state': state, 'device_id': _currentPlayer.deviceID}, options: Options( headers: { 'Content-Type': 'application/json', @@ -462,17 +464,17 @@ class SpotifySdkPlugin { ), ); } + /// Toggles repeat on the current player. Future toggleRepeat(bool state) async { - if(_currentPlayer?.deviceID == null) { - throw PlatformException(message: "Spotify player not connected!", code: "Playback Error"); + if (_currentPlayer?.deviceID == null) { + throw PlatformException( + message: "Spotify player not connected!", code: "Playback Error"); } - await _dio.put('https://api.spotify.com/v1/me/player/repeat', - queryParameters: { - 'state': state, - 'device_id': _currentPlayer.deviceID - }, + await _dio.put( + 'https://api.spotify.com/v1/me/player/repeat', + queryParameters: {'state': state, 'device_id': _currentPlayer.deviceID}, options: Options( headers: { 'Content-Type': 'application/json', @@ -481,14 +483,15 @@ class SpotifySdkPlugin { ), ); } + /// Converts a native WebPlaybackState to the library PlayerState PlayerState toPlayerState(WebPlaybackState state) { - if(state == null) return null; + if (state == null) return null; WebPlaybackTrack trackRaw = state.track_window?.current_track; WebPlaybackAlbum albumRaw = trackRaw?.album; WebPlayerDisallows restrictionsRaw = state.disallows; List artists = []; - for(var artist in trackRaw.artists) { + for (var artist in trackRaw.artists) { artists.add(Artist(artist.name, artist.uri)); } @@ -507,45 +510,39 @@ class SpotifySdkPlugin { } return PlayerState( - trackRaw != null - ? Track ( - Album( - albumRaw.name, - albumRaw.uri - ), - artists[0], - artists, - null, - ImageUri(albumRaw.images[0].url), - false, - false, - trackRaw.name, - trackRaw.uri - ) - : null, - state.paused, - 1.0, - state.position, - options.PlayerOptions(state.shuffle, repeatMode), - PlayerRestrictions( - restrictionsRaw.skipping_next, - restrictionsRaw.skipping_prev, - false, - false, - false, - restrictionsRaw.seeking - ) - ); + trackRaw != null + ? Track( + Album(albumRaw.name, albumRaw.uri), + artists[0], + artists, + null, + ImageUri(albumRaw.images[0].url), + false, + false, + trackRaw.name, + trackRaw.uri) + : null, + state.paused, + 1.0, + state.position, + options.PlayerOptions(state.shuffle, repeatMode), + PlayerRestrictions( + restrictionsRaw.skipping_next, + restrictionsRaw.skipping_prev, + false, + false, + false, + restrictionsRaw.seeking)); } + /// Converts a native WebPlaybackState to the library PlayerContext PlayerContext toPlayerContext(WebPlaybackState state) { - if(state == null) return null; + if (state == null) return null; return PlayerContext( - state.context.metadata.title, - state.context.metadata.subtitle, - state.context.metadata.type, - state.context.uri - ); + state.context.metadata.title, + state.context.metadata.subtitle, + state.context.metadata.type, + state.context.uri); } } @@ -557,33 +554,47 @@ class Player { /// The main constructor for initializing the Web Playback SDK. It should contain an object with the player name, volume and access token. external Player(PlayerOptions options); + /// Connects Web Playback SDK instance to Spotify with the credentials provided during initialization. external dynamic connect(); + /// Closes the current session that Web Playback SDK has with Spotify. - external void disconnect(); + external void disconnect(); + /// Create a new event listener in the Web Playback SDK. external void addListener(String type, Function callback); + /// Remove an event listener in the Web Playback SDK. external void removeListener(String event_name); + /// Collect metadata on local playback. external dynamic getCurrentState(); + /// Rename the Spotify Player device. This is visible across all Spotify Connect devices. external dynamic setName(String name); + /// Set the local volume for the Web Playback SDK. external dynamic setVolume(double volume); + /// Pause the local playback. external dynamic pause(); + /// Resume the local playback. external dynamic resume(); + /// Resume/pause the local playback. external dynamic togglePlay(); + /// Seek to a position in the current track in local playback. external dynamic seek(int position_ms); + /// Switch to the previous track in local playback. external dynamic previousTrack(); + /// Skip to the next track in local playback. external dynamic nextTrack(); } + @JS() @anonymous class PlayerOptions { @@ -591,8 +602,10 @@ class PlayerOptions { external Function get getOAuthToken; external double get volume; - external factory PlayerOptions({String name, Function getOAuthToken, double volume}); + external factory PlayerOptions( + {String name, Function getOAuthToken, double volume}); } + @JS() @anonymous class WebPlaybackPlayer { @@ -600,9 +613,10 @@ class WebPlaybackPlayer { external factory WebPlaybackPlayer({String device_id}); } + @JS() @anonymous -class WebPlaybackState { +class WebPlaybackState { external WebPlayerContext get context; external WebPlayerDisallows get disallows; external bool get paused; @@ -611,16 +625,26 @@ class WebPlaybackState { external bool get shuffle; external WebPlayerTrackWindow get track_window; - external factory WebPlaybackState({WebPlayerContext context, WebPlayerDisallows disallows, bool paysed, int position, int repeat_mode, bool shuffle, WebPlayerTrackWindow track_window}); + external factory WebPlaybackState( + {WebPlayerContext context, + WebPlayerDisallows disallows, + bool paysed, + int position, + int repeat_mode, + bool shuffle, + WebPlayerTrackWindow track_window}); } + @JS() @anonymous -class WebPlayerContext { +class WebPlayerContext { external String get uri; external WebPlayerContextMetadata get metadata; - external factory WebPlayerContext({String uri, WebPlayerContextMetadata metadata}); + external factory WebPlayerContext( + {String uri, WebPlayerContextMetadata metadata}); } + @JS() @anonymous class WebPlayerContextMetadata { @@ -628,11 +652,13 @@ class WebPlayerContextMetadata { external String get subtitle; external String get type; - external factory WebPlayerContextMetadata({String title, String subtitle, String type}); + external factory WebPlayerContextMetadata( + {String title, String subtitle, String type}); } + @JS() @anonymous -class WebPlayerDisallows { +class WebPlayerDisallows { external bool get pausing; external bool get peeking_next; external bool get peeking_prev; @@ -641,17 +667,29 @@ class WebPlayerDisallows { external bool get skipping_next; external bool get skipping_prev; - external factory WebPlayerDisallows({bool pausing, bool peeking_next, bool peeking_prev, bool resuming, bool seeking, bool skipping_next, bool skipping_prev}); + external factory WebPlayerDisallows( + {bool pausing, + bool peeking_next, + bool peeking_prev, + bool resuming, + bool seeking, + bool skipping_next, + bool skipping_prev}); } + @JS() @anonymous -class WebPlayerTrackWindow { +class WebPlayerTrackWindow { external WebPlaybackTrack get current_track; external List get previous_tracks; external List get next_tracks; - external factory WebPlayerTrackWindow({WebPlaybackTrack current_track, List previous_tracks, List next_tracks}); + external factory WebPlayerTrackWindow( + {WebPlaybackTrack current_track, + List previous_tracks, + List next_tracks}); } + @JS() @anonymous class WebPlaybackTrack { @@ -664,8 +702,17 @@ class WebPlaybackTrack { external WebPlaybackAlbum get album; external List get artists; - external factory WebPlaybackTrack({String uri, String id, String type, String media_type, String name, bool is_playable, WebPlaybackAlbum album, List artists}); + external factory WebPlaybackTrack( + {String uri, + String id, + String type, + String media_type, + String name, + bool is_playable, + WebPlaybackAlbum album, + List artists}); } + @JS() @anonymous class WebPlaybackAlbum { @@ -673,8 +720,10 @@ class WebPlaybackAlbum { external String get name; external List get images; - external factory WebPlaybackAlbum({String uri, String name, List images}); + external factory WebPlaybackAlbum( + {String uri, String name, List images}); } + @JS() @anonymous class WebPlaybackArtist { @@ -683,6 +732,7 @@ class WebPlaybackArtist { external factory WebPlaybackArtist({String uri, String name}); } + @JS() @anonymous class WebPlaybackAlbumImage { @@ -690,6 +740,7 @@ class WebPlaybackAlbumImage { external factory WebPlaybackAlbumImage({String url}); } + @JS() @anonymous class WebPlaybackError { @@ -697,9 +748,10 @@ class WebPlaybackError { external factory WebPlaybackError({String message}); } + class SpotifyToken { final String token; final int expiry; SpotifyToken(this.token, this.expiry); -} \ No newline at end of file +} diff --git a/pubspec.yaml b/pubspec.yaml index 35fe0143..5c5a4d8e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,21 +6,22 @@ issue_tracker: https://github.com/brim-borium/spotify_sdk/issues environment: sdk: ">=2.7.0 <3.0.0" + flutter: ">=1.12.0 <2.0.0" dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter - logger: ^0.8.0 - dio: any + logger: ^0.9.1 + dio: ^3.0.9 json_annotation: ^3.0.0 - js: ^0.6.1+1 + js: ^0.6.2 dev_dependencies: flutter_test: sdk: flutter - build_runner: ^1.0.0 + build_runner: ^1.10.0 json_serializable: ^3.2.0 pedantic: ^1.8.0+1 @@ -43,7 +44,6 @@ flutter: web: pluginClass: SpotifySdkPlugin fileName: spotify_sdk_web.dart - # To add assets to your plugin package, add an assets section, like this: # assets: From be28a5c782f7267af74ecdf6fab29fa328572c39 Mon Sep 17 00:00:00 2001 From: Tobias Busch Date: Tue, 7 Jul 2020 08:00:54 +0200 Subject: [PATCH 41/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ecc309ec..e2f843e2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spotify_sdk [![pub package](https://img.shields.io/badge/pub-0.3.4-orange)](https://pub.dev/packages/spotify_sdk) -[![Test and Build](https://github.com/brim-borium/spotify_sdk/workflows/Test%20and%20Build/badge.svg?branch=develop)](https://github.com/brim-borium/spotify_sdk/actions) +![Dry Run](https://github.com/brim-borium/spotify_sdk/workflows/Dry%20Run/badge.svg?branch=develop) [![licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://github.com/IamTobi/spotify_sdk/blob/master/LICENSE) ## Description From 7745ffca67446cfd2172ae084ca4137d1771fa1d Mon Sep 17 00:00:00 2001 From: Arnav Shrivastava <47694237+arnav-sh@users.noreply.github.com> Date: Tue, 7 Jul 2020 14:29:49 +0530 Subject: [PATCH 42/47] Update README.md Co-authored-by: Foti Dim --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24b5c62b..6cad4c7d 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ To start using this package you first have to connect to Spotify. To only connec await SpotifySdk.connectToSpotifyRemote(clientId: "", redirectUrl: "") ``` -If you want to use the web api aswell you have to use this method to get the authentication token. +If you want to use the web api as well you have to use this method to get the authentication token. You can specify multiple scopes by seperating them with a comma "," as shown below. For more information on scopes you can refer [Spotify Authorization Scopes Guide](https://developer.spotify.com/documentation/general/guides/scopes/) ```dart From 022154da54936626b1d3c14fc66586d7da14b57e Mon Sep 17 00:00:00 2001 From: Arnav Shrivastava <47694237+arnav-sh@users.noreply.github.com> Date: Tue, 7 Jul 2020 14:30:43 +0530 Subject: [PATCH 43/47] Update README.md Co-authored-by: Foti Dim --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cad4c7d..373ea742 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ await SpotifySdk.connectToSpotifyRemote(clientId: "", redirectUrl: "") ``` If you want to use the web api as well you have to use this method to get the authentication token. -You can specify multiple scopes by seperating them with a comma "," as shown below. For more information on scopes you can refer [Spotify Authorization Scopes Guide](https://developer.spotify.com/documentation/general/guides/scopes/) +You can specify multiple scopes by seperating them with a comma "," as shown below. For more information on scopes you can refer to [Spotify Authorization Scopes Guide](https://developer.spotify.com/documentation/general/guides/scopes/) ```dart var authenticationToken = await SpotifySdk.getAuthenticationToken(clientId: "", redirectUrl: "", scope: "app-remote-control,user-modify-playback-state,playlist-read-private"); From ade8c32deccdd03569b95a81578016184354dc24 Mon Sep 17 00:00:00 2001 From: Tobias Busch Date: Wed, 8 Jul 2020 10:21:57 +0200 Subject: [PATCH 44/47] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 373ea742..9c79efa9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # spotify_sdk -[![pub package](https://img.shields.io/badge/pub-0.3.4-orange)](https://pub.dev/packages/spotify_sdk) -![Dry Run](https://github.com/brim-borium/spotify_sdk/workflows/Dry%20Run/badge.svg?branch=develop) -[![licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://github.com/IamTobi/spotify_sdk/blob/master/LICENSE) +[![pub package](https://img.shields.io/badge/pub-0.3.4-orange)](https://pub.dev/packages/spotify_sdk)] +[![Dry Run](https://github.com/brim-borium/spotify_sdk/workflows/Dry%20Run/badge.svg?branch=develop)](https://github.com/brim-borium/spotify_sdk/actions?query=workflow%3A%22Dry+Run%22) +[![licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://github.com/IamTobi/spotify_sdk/blob/master/LICENSE)] ## Description From 93378771d437919293c71528e3cf16a9a529f72a Mon Sep 17 00:00:00 2001 From: Tobias Busch Date: Wed, 8 Jul 2020 10:24:14 +0200 Subject: [PATCH 45/47] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9c79efa9..e3faa67a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # spotify_sdk -[![pub package](https://img.shields.io/badge/pub-0.3.4-orange)](https://pub.dev/packages/spotify_sdk)] -[![Dry Run](https://github.com/brim-borium/spotify_sdk/workflows/Dry%20Run/badge.svg?branch=develop)](https://github.com/brim-borium/spotify_sdk/actions?query=workflow%3A%22Dry+Run%22) -[![licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://github.com/IamTobi/spotify_sdk/blob/master/LICENSE)] +[![pub package](https://img.shields.io/badge/pub-0.3.4-orange)](https://pub.dev/packages/spotify_sdk) +[![Dry Run](https://github.com/brim-borium/spotify_sdk/workflows/Dry%20Run/badge.svg?branch=develop)](https://github.com/brim-borium/spotify_sdk/actions?query=workflow%3A%22Dry+Run%22)] +[![licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://github.com/IamTobi/spotify_sdk/blob/master/LICENSE) ## Description From b102c7b30b711e3bbd247f9fc2da432b39f08e38 Mon Sep 17 00:00:00 2001 From: Tobias Busch Date: Wed, 8 Jul 2020 10:24:39 +0200 Subject: [PATCH 46/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e3faa67a..a332f46a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # spotify_sdk [![pub package](https://img.shields.io/badge/pub-0.3.4-orange)](https://pub.dev/packages/spotify_sdk) -[![Dry Run](https://github.com/brim-borium/spotify_sdk/workflows/Dry%20Run/badge.svg?branch=develop)](https://github.com/brim-borium/spotify_sdk/actions?query=workflow%3A%22Dry+Run%22)] +[![Dry Run](https://github.com/brim-borium/spotify_sdk/workflows/Dry%20Run/badge.svg?branch=develop)](https://github.com/brim-borium/spotify_sdk/actions?query=workflow%3A%22Dry+Run%22) [![licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://github.com/IamTobi/spotify_sdk/blob/master/LICENSE) ## Description From cf7ed9c65088660a6ce4783967d804838d54885c Mon Sep 17 00:00:00 2001 From: Tobias Busch Date: Wed, 8 Jul 2020 18:42:06 +0200 Subject: [PATCH 47/47] upgrades to 0.5.0 --- CHANGELOG.md | 7 + README.md | 14 +- analysis_options.yaml | 16 +- .../minimalme/spotify_sdk/SpotifySdkPlugin.kt | 23 +- example/android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- example/lib/main.dart | 291 ++++++++++-------- lib/spotify_sdk.dart | 40 ++- lib/spotify_sdk_web.dart | 14 +- pubspec.yaml | 4 +- 10 files changed, 231 insertions(+), 184 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b22060..35c6ff43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.5.0 +* adds support for web (thanks [itsMatoosh](https://github.com/itsMatoosh)) +* adds custom scopes for the web api (thanks [arnav-sh](https://github.com/arnav-sh)) +* adds logout functionality for android +* moved from [pedantic](https://pub.dev/packages/pedantic) to [lint](https://pub.dev/packages/lint) for static analyses +* some minor bug fixing + ## 0.3.4 * adds handling of unexpected disconnects from Spotify via subscribeConnectionStatus()-Stream(thanks [itsMatoosh](https://github.com/itsMatoosh)) diff --git a/README.md b/README.md index a332f46a..3808dec4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # spotify_sdk -[![pub package](https://img.shields.io/badge/pub-0.3.4-orange)](https://pub.dev/packages/spotify_sdk) +[![pub package](https://img.shields.io/badge/pub-0.5.0-orange)](https://pub.dev/packages/spotify_sdk) [![Dry Run](https://github.com/brim-borium/spotify_sdk/workflows/Dry%20Run/badge.svg?branch=develop)](https://github.com/brim-borium/spotify_sdk/actions?query=workflow%3A%22Dry+Run%22) [![licence](https://img.shields.io/badge/licence-MIT-blue.svg)](https://github.com/IamTobi/spotify_sdk/blob/master/LICENSE) @@ -70,19 +70,17 @@ Important here is the naming so that the package can find the modules. To start using this package you first have to connect to Spotify. To only connect you can do this with connectToSpotifyRemote(...) or getAuthenticationToken(...) in both of these methods you need the client id, given in the spotify dashboard and the redirect url you set in the settings on the dashboard. ```dart -await SpotifySdk.connectToSpotifyRemote(clientId: "", redirectUrl: "") + await SpotifySdk.connectToSpotifyRemote(clientId: "", redirectUrl: "") ``` If you want to use the web api as well you have to use this method to get the authentication token. -You can specify multiple scopes by seperating them with a comma "," as shown below. For more information on scopes you can refer to [Spotify Authorization Scopes Guide](https://developer.spotify.com/documentation/general/guides/scopes/) +You can specify multiple scopes by separating them with a comma "," as shown below. For more information on scopes you can refer to [Spotify Authorization Scopes Guide](https://developer.spotify.com/documentation/general/guides/scopes/) ```dart -var authenticationToken = await SpotifySdk.getAuthenticationToken(clientId: "", redirectUrl: "", scope: "app-remote-control,user-modify-playback-state,playlist-read-private"); + var authenticationToken = await SpotifySdk.getAuthenticationToken(clientId: "", redirectUrl: "", scope: "app-remote-control,user-modify-playback-state,playlist-read-private"); ``` -tbd... - -Have a look [here](example/lib/main.dart) for a more detailed example. +Have a look [in the example](example/lib/main.dart) for detailed insights on how you can use this package. ### Api @@ -92,7 +90,7 @@ Have a look [here](example/lib/main.dart) for a more detailed example. |---|---|---|---|---| | connectToSpotifyRemote | Connects the App to Spotify | :heavy_check_mark: | :construction_worker: | :heavy_check_mark: | | getAuthenticationToken | Gets the Authentication Token that you can use to work with the [Web Api](https://developer.spotify.com/documentation/web-api/) |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | -| logout | logs the user out and disconnects the app connection |:construction_worker: | :construction_worker: | :heavy_check_mark: | +| logout | logs the user out and disconnects the app connection |:heavy_check_mark: | :construction_worker: | :heavy_check_mark: | #### Player Api diff --git a/analysis_options.yaml b/analysis_options.yaml index 68805cee..11384d0d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,14 +1,14 @@ -include: package:pedantic/analysis_options.yaml +include: package:lint/analysis_options_package.yaml linter: - rules: - # ------ Disable individual rules ----- # - # --- # - # Turn off what you don't like. # - # ------------------------------------- # - + rules: + # ------ Disable individual rules ----- # + # --- # + # Turn off what you don't like. # + # ------------------------------------- # + analyzer: - exclude: + exclude: # Ignore generated files - '**/*.g.dart' - 'lib/src/generated/*.dart' \ No newline at end of file diff --git a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt index a8b1de91..2953c149 100644 --- a/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt +++ b/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkPlugin.kt @@ -6,7 +6,6 @@ import com.spotify.android.appremote.api.ConnectionParams import com.spotify.android.appremote.api.Connector.ConnectionListener import com.spotify.android.appremote.api.SpotifyAppRemote import com.spotify.android.appremote.api.error.* -import com.spotify.protocol.client.Subscription import com.spotify.sdk.android.auth.AuthorizationClient import com.spotify.sdk.android.auth.AuthorizationRequest import com.spotify.sdk.android.auth.AuthorizationResponse @@ -20,7 +19,6 @@ import io.flutter.plugin.common.PluginRegistry import io.flutter.plugin.common.PluginRegistry.Registrar import kotlinx.event.SetEvent import kotlinx.event.event -import java.nio.channels.Channel class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, PluginRegistry.ActivityResultListener { @@ -164,7 +162,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl .build() var replySubmitted = false SpotifyAppRemote.disconnect(spotifyAppRemote) - var initiallyConnected = false; + var initiallyConnected = false SpotifyAppRemote.connect(registrar.context(), connectionParams, object : ConnectionListener { override fun onConnected(spotifyAppRemoteValue: SpotifyAppRemote) { @@ -174,7 +172,7 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl capabilitiesChannel.setStreamHandler(CapabilitiesChannel(spotifyAppRemote!!.userApi)) userStatusChannel.setStreamHandler(UserStatusChannel(spotifyAppRemote!!.userApi)) - initiallyConnected = true; + initiallyConnected = true // emit connection established event connStatusEventChannel(ConnectionStatusChannel.ConnectionEvent(true, "Successfully connected to Spotify.", null, null)) // method success @@ -185,8 +183,8 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl override fun onFailure(throwable: Throwable) { val errorDetails = throwable.toString() // determine the error - var errorMessage: String - var errorCode: String + val errorMessage: String + val errorCode: String var connected = false when (throwable) { is SpotifyDisconnectedException, is SpotifyConnectionTerminatedException -> { @@ -238,12 +236,12 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl } Log.e("SPOTIFY_SDK", errorMessage) // notify plugin - if (initiallyConnected == true) { + 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); + result.error(errorCode, errorMessage, errorDetails) } } }) @@ -271,18 +269,21 @@ class SpotifySdkPlugin(private val registrar: Registrar) : MethodCallHandler, Pl } private fun logoutFromSpotify(result: Result) { - if (spotifyAppRemote != null) { + 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 { + } + 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") } } - //-- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { if (pendingOperation == null) { diff --git a/example/android/build.gradle b/example/android/build.gradle index 13546311..04ec3490 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' + classpath 'com.android.tools.build:gradle:4.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 14eb49b9..55dde6dd 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Mar 04 15:55:14 CET 2020 +#Wed Jul 08 17:57:20 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/example/lib/main.dart b/example/lib/main.dart index 395a0c01..4083804b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,17 +1,15 @@ +import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'dart:async'; - import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:logger/logger.dart'; import 'package:spotify_sdk/models/crossfade_state.dart'; -import 'package:spotify_sdk/spotify_sdk.dart'; -import 'package:spotify_sdk/models/player_state.dart'; -import 'package:spotify_sdk/models/player_context.dart'; -import 'package:spotify_sdk/models/connection_status.dart'; import 'package:spotify_sdk/models/image_uri.dart'; -import 'package:logger/logger.dart'; +import 'package:spotify_sdk/models/player_context.dart'; +import 'package:spotify_sdk/models/player_state.dart'; +import 'package:spotify_sdk/spotify_sdk.dart'; import 'widgets/sized_icon_button.dart'; @@ -39,149 +37,152 @@ class Home extends StatefulWidget { class _HomeState extends State { bool _loading = false; + bool _connected = false; final Logger _logger = Logger(); CrossfadeState crossfadeState; @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("SpotifySdk Example"), - ), - body: _sampleFlowWidget(context), - ); + return StreamBuilder( + stream: SpotifySdk.subscribeConnectionStatus(), + builder: (context, snapshot) { + _connected = false; + if (snapshot.data != null) { + _connected = snapshot.data.connected; + } + return Scaffold( + appBar: AppBar( + title: Text("SpotifySdk Example"), + actions: [ + _connected + ? FlatButton( + child: Text("Logout"), onPressed: () => logout()) + : Container() + ], + ), + body: _sampleFlowWidget(context), + ); + }); } Widget _sampleFlowWidget(BuildContext context2) { - return StreamBuilder( - stream: SpotifySdk.subscribeConnectionStatus(), - builder: (context, snapshot) { - bool _connected = false; - if (snapshot.data != null) { - _connected = snapshot.data.connected; - } - - return Stack( + return Stack( + children: [ + ListView( + padding: EdgeInsets.all(8), children: [ - ListView( - padding: EdgeInsets.all(8), - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - FlatButton( - child: Icon(Icons.settings_remote), - onPressed: () => connectToSpotifyRemote(), - ), - FlatButton( - child: Text("get auth token "), - onPressed: () => getAuthenticationToken(), - ), - ], + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FlatButton( + child: Icon(Icons.settings_remote), + onPressed: () => connectToSpotifyRemote(), ), - Divider(), - Text("Player State", style: TextStyle(fontSize: 16)), - _connected - ? PlayerStateWidget() - : Center( - child: Text("Not connected"), - ), - Divider(), - Text("Player Context", style: TextStyle(fontSize: 16)), - _connected - ? PlayerContextWidget() - : Center( - child: Text("Not connected"), - ), - Divider(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SizedIconButton( - width: 50, - icon: Icons.skip_previous, - onPressed: () => skipPrevious(), - ), - SizedIconButton( - width: 50, - icon: Icons.play_arrow, - onPressed: () => resume(), - ), - SizedIconButton( - width: 50, - icon: Icons.pause, - onPressed: () => pause(), - ), - SizedIconButton( - width: 50, - icon: Icons.skip_next, - onPressed: () => skipNext(), - ), - ], + FlatButton( + child: Text("get auth token "), + onPressed: () => getAuthenticationToken(), ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SizedIconButton( - width: 50, - icon: Icons.queue_music, - onPressed: () => queue(), - ), - SizedIconButton( - width: 50, - icon: Icons.play_circle_filled, - onPressed: () => play(), - ), - SizedIconButton( - width: 50, - icon: Icons.repeat, - onPressed: () => toggleRepeat(), - ), - SizedIconButton( - width: 50, - icon: Icons.shuffle, - onPressed: () => toggleShuffle(), - ), - ], + ], + ), + Divider(), + Text("Player State", style: TextStyle(fontSize: 16)), + _connected + ? playerStateWidget() + : Center( + child: Text("Not connected"), + ), + Divider(), + Text("Player Context", style: TextStyle(fontSize: 16)), + _connected + ? playerContextWidget() + : Center( + child: Text("Not connected"), + ), + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedIconButton( + width: 50, + icon: Icons.skip_previous, + onPressed: () => skipPrevious(), ), - FlatButton( - child: Icon(Icons.favorite), - onPressed: () => addToLibrary()), - Row( - children: [ - FlatButton( - child: Text("seek to"), onPressed: () => seekTo()), - FlatButton( - child: Text("seek to relative"), - onPressed: () => seekToRelative()), - ], + SizedIconButton( + width: 50, + icon: Icons.play_arrow, + onPressed: () => resume(), + ), + SizedIconButton( + width: 50, + icon: Icons.pause, + onPressed: () => pause(), ), - Divider(), - Text("Crossfade State", style: TextStyle(fontSize: 16)), + SizedIconButton( + width: 50, + icon: Icons.skip_next, + onPressed: () => skipNext(), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedIconButton( + width: 50, + icon: Icons.queue_music, + onPressed: () => queue(), + ), + SizedIconButton( + width: 50, + icon: Icons.play_circle_filled, + onPressed: () => play(), + ), + SizedIconButton( + width: 50, + icon: Icons.repeat, + onPressed: () => toggleRepeat(), + ), + SizedIconButton( + width: 50, + icon: Icons.shuffle, + onPressed: () => toggleShuffle(), + ), + ], + ), + FlatButton( + child: Icon(Icons.favorite), onPressed: () => addToLibrary()), + Row( + children: [ + FlatButton(child: Text("seek to"), onPressed: () => seekTo()), FlatButton( - child: Text("getCrossfadeState"), - onPressed: () => getCrossfadeState()), - Text("Is enabled: ${crossfadeState?.isEnabled}"), - Text("Duration: ${crossfadeState?.duration}"), - Divider(), - _connected - ? SpotifyImageWidget() - : Text('Connect to see an image...'), + child: Text("seek to relative"), + onPressed: () => seekToRelative()), ], ), - _loading - ? Container( - color: Colors.black12, - child: Center(child: CircularProgressIndicator())) - : SizedBox(), + Divider(), + Text("Crossfade State", style: TextStyle(fontSize: 16)), + FlatButton( + child: Text("getCrossfadeState"), + onPressed: () => getCrossfadeState()), + Text("Is enabled: ${crossfadeState?.isEnabled}"), + Text("Duration: ${crossfadeState?.duration}"), + Divider(), + _connected + ? spotifyImageWidget() + : Text('Connect to see an image...'), ], - ); - }, + ), + _loading + ? Container( + color: Colors.black12, + child: Center(child: CircularProgressIndicator())) + : SizedBox(), + ], ); } - Widget PlayerStateWidget() { + Widget playerStateWidget() { return StreamBuilder( stream: SpotifySdk.subscribePlayerState(), initialData: PlayerState(null, false, 1, 1, null, null), @@ -214,7 +215,7 @@ class _HomeState extends State { ); } - Widget PlayerContextWidget() { + Widget playerContextWidget() { return StreamBuilder( stream: SpotifySdk.subscribePlayerContext(), initialData: PlayerContext("", "", "", ""), @@ -240,7 +241,7 @@ class _HomeState extends State { ); } - Widget SpotifyImageWidget() { + Widget spotifyImageWidget() { return FutureBuilder( future: SpotifySdk.getImage( imageUri: ImageUri( @@ -267,6 +268,29 @@ class _HomeState extends State { }); } + Future logout() async { + try { + setState(() { + _loading = true; + }); + var result = await SpotifySdk.logout(); + setStatus(result ? "logout successful" : "logout failed"); + setState(() { + _loading = false; + }); + } on PlatformException catch (e) { + setState(() { + _loading = false; + }); + setStatus(e.code, message: e.message); + } on MissingPluginException { + setState(() { + _loading = false; + }); + setStatus("not implemented"); + } + } + Future connectToSpotifyRemote() async { try { setState(() { @@ -297,7 +321,10 @@ class _HomeState extends State { Future getAuthenticationToken() async { try { var authenticationToken = await SpotifySdk.getAuthenticationToken( - clientId: "", redirectUrl: ""); + clientId: DotEnv().env['CLIENT_ID'], + redirectUrl: DotEnv().env['REDIRECT_URL'], + scope: + "app-remote-control, user-modify-playback-state, playlist-read-private, playlist-modify-public,user-read-currently-playing"); setStatus("Got a token: $authenticationToken"); } on PlatformException catch (e) { setStatus(e.code, message: e.message); diff --git a/lib/spotify_sdk.dart b/lib/spotify_sdk.dart index 8fa7b0ac..1eefd879 100644 --- a/lib/spotify_sdk.dart +++ b/lib/spotify_sdk.dart @@ -5,10 +5,10 @@ import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:logger/logger.dart'; +import 'package:spotify_sdk/models/image_uri.dart'; import 'package:spotify_sdk/models/library_state.dart'; import 'package:spotify_sdk/models/player_state.dart'; import 'package:spotify_sdk/models/user_status.dart'; -import 'package:spotify_sdk/models/image_uri.dart'; import 'models/capabilities.dart'; import 'models/connection_status.dart'; @@ -49,7 +49,8 @@ class SpotifySdk { static const String _methodSeekToRelativePosition = "seekToRelativePosition"; static const String _methodSubscribePlayerContext = "subscribePlayerContext"; static const String _methodSubscribePlayerState = "subscribePlayerState"; - static const String _methodSubscribeConnectionStatus = "subscribeConnectionStatus"; + static const String _methodSubscribeConnectionStatus = + "subscribeConnectionStatus"; static const String _methodToggleRepeat = "toggleRepeat"; static const String _methodToggleShuffle = "toggleShuffle"; // user api @@ -74,32 +75,45 @@ class SpotifySdk { /// Connects to Spotify Remote, returning a [bool] for confirmation /// - /// Required paramters are the [clientId] and the [redirectUrl] to authenticate with the Spotify Api + /// Required parameters are the [clientId] and the [redirectUrl] to authenticate with the Spotify Api /// Throws a [PlatformException] if connecting to the remote api failed /// Throws a [MissingPluginException] if the method is not implemented on the native platforms. static Future connectToSpotifyRemote( - {@required String clientId, @required String redirectUrl, String playerName = 'Spotify SDK'}) async { + {@required String clientId, + @required String redirectUrl, + String playerName = 'Spotify SDK'}) async { try { - return await _channel.invokeMethod(_methodConnectToSpotify, - {_paramClientId: clientId, _paramRedirectUrl: redirectUrl, _paramPlayerName: playerName}); + return await _channel.invokeMethod(_methodConnectToSpotify, { + _paramClientId: clientId, + _paramRedirectUrl: redirectUrl, + _paramPlayerName: playerName + }); } on Exception catch (e) { _logException(_methodConnectToSpotify, e); rethrow; } } - /// Returns an authentication token as a [String]. + /// Returns an authentication token as a [String] /// - /// Required paramters are the [clientId] and the [redirectUrl] to authenticate with the Spotify Api. + /// Required parameters are the [clientId] and the [redirectUrl] to authenticate with the Spotify Api. + /// Also you have to provide a [scope] like + /// "app-remote-control, user-modify-playback-state, playlist-read-private, playlist-modify-public,user-read-currently-playing" + /// See https://developer.spotify.com/documentation/general/guides/scopes/ for more scopes and how to use them /// The token can be used to communicate with the web api /// Throws a [PlatformException] if retrieving the authentication token failed. /// Throws a [MissingPluginException] if the method is not implemented on the native platforms. static Future getAuthenticationToken( - {@required String clientId, @required String redirectUrl, @required String scope}) async { + {@required String clientId, + @required String redirectUrl, + @required String scope}) async { try { final String authorization = await _channel.invokeMethod( - _methodGetAuthenticationToken, - {_paramClientId: clientId, _paramRedirectUrl: redirectUrl, _paramScope: scope}); + _methodGetAuthenticationToken, { + _paramClientId: clientId, + _paramRedirectUrl: redirectUrl, + _paramScope: scope + }); return authorization; } on Exception catch (e) { _logException(_methodGetAuthenticationToken, e); @@ -111,9 +125,9 @@ class SpotifySdk { /// /// Throws a [PlatformException] if logout failed /// Throws a [MissingPluginException] if the method is not implemented on the native platforms. - static Future logout() async { + static Future logout() async { try { - await _channel.invokeMethod(_methodLogoutFromSpotify); + return await _channel.invokeMethod(_methodLogoutFromSpotify); } on Exception catch (e) { _logException(_methodLogoutFromSpotify, e); rethrow; diff --git a/lib/spotify_sdk_web.dart b/lib/spotify_sdk_web.dart index 387e04c5..c437b644 100644 --- a/lib/spotify_sdk_web.dart +++ b/lib/spotify_sdk_web.dart @@ -177,9 +177,9 @@ class SpotifySdkPlugin { return true; } // update the client id and redirect url - String clientId = call.arguments[PARAM_CLIENT_ID]; - String redirectUrl = call.arguments[PARAM_REDIRECT_URL]; - String playerName = call.arguments[PARAM_PLAYER_NAME]; + String clientId = call.arguments[PARAM_CLIENT_ID] as String; + String redirectUrl = call.arguments[PARAM_REDIRECT_URL] as String; + String playerName = call.arguments[PARAM_PLAYER_NAME] as String; if (!(clientId?.isNotEmpty == true && redirectUrl?.isNotEmpty == true)) { throw PlatformException( @@ -219,8 +219,8 @@ class SpotifySdkPlugin { break; case METHOD_GET_AUTHENTICATION_TOKEN: return await _getSpotifyAuthToken( - clientId: call.arguments[PARAM_CLIENT_ID], - redirectUrl: call.arguments[PARAM_REDIRECT_URL]); + clientId: call.arguments[PARAM_CLIENT_ID] as String, + redirectUrl: call.arguments[PARAM_REDIRECT_URL] as String); break; case METHOD_LOGOUT_FROM_SPOTIFY: log('Disconnecting from Spotify...'); @@ -234,10 +234,10 @@ class SpotifySdkPlugin { } break; case METHOD_PLAY: - await _play(call.arguments[PARAM_SPOTIFY_URI]); + await _play(call.arguments[PARAM_SPOTIFY_URI] as String); break; case METHOD_QUEUE_TRACK: - await _queue(call.arguments[PARAM_SPOTIFY_URI]); + await _queue(call.arguments[PARAM_SPOTIFY_URI] as String); break; case METHOD_RESUME: await promiseToFuture(_currentPlayer?.resume()); diff --git a/pubspec.yaml b/pubspec.yaml index 5c5a4d8e..28efc4f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: spotify_sdk description: A flutter plugin that let's you communicate with the spotify sdk and auth lib -version: 0.3.4 +version: 0.5.0 homepage: https://github.com/brim-borium/spotify_sdk issue_tracker: https://github.com/brim-borium/spotify_sdk/issues @@ -23,7 +23,7 @@ dev_dependencies: sdk: flutter build_runner: ^1.10.0 json_serializable: ^3.2.0 - pedantic: ^1.8.0+1 + lint: ^1.0.0