brim-borium / spotify_sdk

Flutter Package to connect the spotify sdk
https://pub.dev/packages/spotify_sdk
Apache License 2.0
154 stars 82 forks source link

Currently-selected song begins playing when connecting to Spotify on iOS #86

Open mcookhome opened 3 years ago

mcookhome commented 3 years ago

Describe the bug

When opening an app integrating the SDK on iOS (for two iPhones with different versions), the Spotify app opens and plays the current song. Using breakpoints, I was able to pinpoint the last line of code before the Spotify app was opened to within the SDK here.

To Reproduce Steps to reproduce the behavior:

  1. Connect from your app to Spotify using these lines of the SDK on iOS
  2. Accept Spotify permissions for your app, if this is the first login (if not, the permissions dialog will not show)
  3. Your currently selected song will start playing,
  4. Focus will be toggled back to your app, with music still playing

Expected behavior I would hope for the app to be able to connect to Spotify without opening the app and toggling the play state. However, according to @fotiDim, the Spotify app must still be opened regardless - but the music being played should be fixable.

Screenshots

https://user-images.githubusercontent.com/5422547/113524940-a5308780-9577-11eb-8b71-8dc75ef3dce8.mp4

Smartphone (please complete the following information):

Additional context Slack conversation

fotiDim commented 3 years ago

@mcookhome this is "potentially" fixed now. Allow me to clarify "potentially"... The native Spotify iOS SDK supports two authentication methods,built-in authorization flow and SPTSessionManager. This library uses exclusively built-in authorization flow for reasons of simplicity and consistency with Android and Web.

When using the built-in authorization flow Spotify will be suspended if it doesn't start playing media immediately. In the native iOS SDK documentation it states:

Why does music need to be playing to connect with SPTAppRemote?
Music must be playing when you connect with SPTAppRemote to ensure the Spotify app is not suspended in the background. iOS applications cannot stay alive in the background indefinitely unless they are actively doing something like navigation or playing music.

There is an official way to mitigate that and that is to use SPTSessionManager. However this is a way more complicated setup as it requires you to setup your own backend component to perform authorization code swap and token refreshing. I have used it in the past (in native code) and I believe the gains do not justify the effort.

Having said that you can try this small workaround that might solve your problem. In version 2.1.0 of the package there is a spotifyUri argument in connectToSpotifyRemote(...) . You can pass an invalid Spotify URI (e.g. spotify:track:58kNJana4w5BIjlZE2wq5r) and then Spotify connects without playing music, at least according to my tests. This is not officially supported so it can fail or stop working at any moment.

mcookhome commented 3 years ago

Awesome!! Thanks so much @fotiDim - can I start using version 2.1.0 now, or do I need to wait for a release? I only see version 2.0.0 on the pub.dev page: image

fotiDim commented 3 years ago

No need to wait. You can use this in your pub spec.yaml to get the latest version from the repo.

spotify_sdk:
  git:
    url: https://github.com/brim-borium/spotify_sdk

Let me know how it works for you.

mcookhome commented 3 years ago

Hey @fotiDim, unfortunately I'm finding the bug to still occur:

Here's a snippet of my pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  spotify_sdk:
    git:
      url: https://github.com/brim-borium/spotify_sdk
  get_it: ^5.0.4

I've run pod install, pod upgrade, re-installed my app multiple times on the target phone, and each time it still plays the targeted song when opening my app. I simply don't know what I'm doing wrong.

I had also tried using version 2.1.0 which had said it offered some of the other changes you'd said you'd made - and that didn't work either. If you want, I'll attach another video of it not working, but I'm not sure if it will help. Do you know what I could be doing wrong?

mcookhome commented 3 years ago

Regardless, I don't think issue should remain closed, as it's still an active issue

fotiDim commented 3 years ago

@mcookhome do you pass a spotifyUri? If yes which one?

mcookhome commented 3 years ago

I don't pass a spotifyUri at all at the moment. here's the snippet of dart code where I connect to spotify:

  Future<void> connectToSpotifyRemoteWithCreds(
      Map<String, dynamic> creds) async {
    try {
      _container.sharedPrefs
          .getToken()
          .then((token) => connectToSpotifyRemoteWithToken(token, creds));
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus('not implemented');
    }
  }

  Future<void> connectToSpotifyRemoteWithToken(
      String token, Map<String, dynamic> creds) async {
    if (token == null) {
      var result = await SpotifySdk.connectToSpotifyRemote(
          clientId: creds["spotify_client_id"],
          redirectUrl: creds["spotify_redirect_url"]);
      setStatus(result
          ? 'connect to spotify successful'
          : 'connect to spotify failed');
      getAuthenticationTokenWithCreds(creds);
      return result;
    } else {
      var result = await SpotifySdk.connectToSpotifyRemote(
          clientId: creds["spotify_client_id"],
          redirectUrl: creds["spotify_redirect_url"],
          accessToken: token);
      setStatus(result
          ? 'connect to spotify successful'
          : 'connect to spotify failed');
      return result;
    }
  }

In that snippet, the creds parameter is populated with my spotify client ID and redirect URI. otherwise, I'm not passing a URI at all.

mcookhome commented 3 years ago

You can also see my attempts at passing an accessToken which we talked about in Slack - but it didn't successfully allow me to avoid opening the app, so I will likely eventually remove that as well.

fotiDim commented 3 years ago

I don't pass a spotifyUri at all at the moment. here's the snippet of dart code where I connect to spotify:

  Future<void> connectToSpotifyRemoteWithCreds(
      Map<String, dynamic> creds) async {
    try {
      _container.sharedPrefs
          .getToken()
          .then((token) => connectToSpotifyRemoteWithToken(token, creds));
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus('not implemented');
    }
  }

  Future<void> connectToSpotifyRemoteWithToken(
      String token, Map<String, dynamic> creds) async {
    if (token == null) {
      var result = await SpotifySdk.connectToSpotifyRemote(
          clientId: creds["spotify_client_id"],
          redirectUrl: creds["spotify_redirect_url"]);
      setStatus(result
          ? 'connect to spotify successful'
          : 'connect to spotify failed');
      getAuthenticationTokenWithCreds(creds);
      return result;
    } else {
      var result = await SpotifySdk.connectToSpotifyRemote(
          clientId: creds["spotify_client_id"],
          redirectUrl: creds["spotify_redirect_url"],
          accessToken: token);
      setStatus(result
          ? 'connect to spotify successful'
          : 'connect to spotify failed');
      return result;
    }
  }

In that snippet, the creds parameter is populated with my spotify client ID and redirect URI. otherwise, I'm not passing a URI at all.

You need to pass a SpotifyURI:

In version 2.1.0 of the package there is a spotifyUri argument in connectToSpotifyRemote(...) . You can pass an invalid Spotify URI (e.g. spotify:track:58kNJana4w5BIjlZE2wq5r) and then Spotify connects without playing music, at least according to my tests. This is not officially supported so it can fail or stop working at any moment.

For the other issue:

mcookhome commented 3 years ago

You need to pass a SpotifyURI:

The problem with that is that if I pass an invalid spotifyUri, then if the music is currently playing on the user's Spotify app, then their music will stop. It will be the inverse of the problem I'm trying to solve. I'd like to be able to check the play state to determine whether or not I'll need to pass the spotifyUri, but to check the play state, I need to connect to the spotify SDK...

Is there any way that the SDK can check the user's current player state, pass the uri to the connect function if the user is not playing music, and not pass the uri to the connect function if the user is playing music?

My desired functionality of the connect method would be as follows:

If this is impossible that's fine, but I just wasn't sure if I was clear.

fotiDim commented 3 years ago

I see. There is this native iOS SDK method that we could expose:

/**
 * Checks if the Spotify app is active on the user's device. You can use this to determine if maybe you should prompt
 * the user to connect to Spotify (because you know they are already using Spotify if it is active). The Spotify app
 * will be considered active if music is playing or the app is active in the background.
 *
 * @param completion Completion block for determining the result of the check. YES if Spotify is active, othewise NO.
 */
+ (void)checkIfSpotifyAppIsActive:(void (^)(BOOL active))completion;

I believe it provides the information that you need. However automating spotifyUri using the logic you described, I believe should be done by the end developer and not this SDK as the native SDK also does not do this.

fotiDim commented 3 years ago

@mcookhome regarding the accessToken issue I reverified that it works. If it does not work for you you might be doing something wrong. If the issue persists please open another issue to keep the discussions separate.

ViktorKirjanov commented 6 months ago

any updates?