51NotePage

Flutter 项目中实现 Spotify API 的一次实践

8 12 月, 2024 | by 51

我正在用 Flutter 做一款 Spotify 综合播放器。我的需求包括一个精简的播放器,但 Spotify SDK 无法获取当前的播放队列(queue)。因此,我选择自己使用 AppAuth 和 http 实现。

在 iOS 平台打包时发现一个重要问题:网页回调(web callback)无法正常工作。即使修改 URL Scheme,授权页面依然无法正确打开应用。这是因为 iOS 上的 Spotify 授权必须通过其官方应用完成。

解决方案

0.5. 原来的使用 AppAuth 实现认证

class SpotifyAuthService {
  final FlutterAppAuth _appAuth;
  final FlutterSecureStorage _secureStorage;
  final String clientId;
  final String clientSecret;
  final String redirectUrl;

  SpotifyAuthService({
    required this.clientId,
    required this.clientSecret,
    required this.redirectUrl,
  }) : _appAuth = const FlutterAppAuth(),
       _secureStorage = const FlutterSecureStorage();

  Future<SpotifyAuthResponse> login({List<String>? scopes}) async {
    try {
      final authResult = await _appAuth.authorize(
        AuthorizationRequest(
          clientId,
          redirectUrl,
          serviceConfiguration: _serviceConfiguration,
          scopes: scopes ?? defaultScopes,
        ),
      );

      final tokenResult = await _appAuth.token(
        TokenRequest(
          clientId,
          redirectUrl,
          authorizationCode: authResult.authorizationCode,
          codeVerifier: authResult.codeVerifier,
          serviceConfiguration: _serviceConfiguration,
          clientSecret: clientSecret,
        ),
      );

      return SpotifyAuthResponse(
        accessToken: tokenResult.accessToken!,
        refreshToken: tokenResult.refreshToken,
        expirationDateTime: tokenResult.accessTokenExpirationDateTime!,
        tokenType: tokenResult.tokenType ?? 'Bearer',
      );
    } catch (e) {
      rethrow;
    }
  }
}

1 使用 Spotify SDK 以适配 iOS

class SpotifyService {
  final String clientId;
  final String redirectUrl;
  final FlutterSecureStorage _secureStorage;

  SpotifyService({
    required this.clientId,
    required this.redirectUrl,
  }) : _secureStorage = const FlutterSecureStorage();

  Future<bool> connect() async {
    try {
      final connected = await SpotifySdk.connectToSpotifyRemote(
        clientId: clientId,
        redirectUrl: redirectUrl,
      );
      
      if (connected) {
        final token = await getAccessToken();
        await _saveAccessToken(token);
      }
      
      return connected;
    } catch (e) {
      throw SpotifyServiceException('Failed to connect to Spotify: $e');
    }
  }

  Future<String> getAccessToken() async {
    try {
      return await SpotifySdk.getAccessToken(
        clientId: clientId,
        redirectUrl: redirectUrl,
        scope: [
          'app-remote-control',
          'playlist-read-private',
          'user-library-read',
        ].join(', '),
      );
    } catch (e) {
      throw SpotifyServiceException('Failed to get access token: $e');
    }
  }

  Future<bool> isAuthenticated() async {
    try {
      final token = await _secureStorage.read(key: _accessTokenKey);
      final expirationStr = await _secureStorage.read(key: _tokenExpirationKey);

      if (token == null || expirationStr == null) return false;

      final expiration = DateTime.parse(expirationStr);
      if (expiration.isBefore(DateTime.now())) {
        final newToken = await getAccessToken(); 
        await _saveAccessToken(newToken);
      }

      return true;
    } catch (e) {
      return false;
    }
  }
}

2. 使用 http 调用 Spotify API,而不是 Spotify SDK

下面是两个核心 API 调用的示例:

// 需要导入 http 包
// 控制随机播放状态
Future<void> setShuffle(bool state) async {
  final headers = await getAuthenticatedHeaders();
  final response = await http.put(
    Uri.parse('https://api.spotify.com/v1/me/player/shuffle?state=$state'),
    headers: headers,
  );

  if (response.statusCode != 200) {
    throw SpotifyAuthException('设置随机播放失败');
  }
}

// 获取当前播放队列
Future<Map<String, dynamic>> getPlaybackQueue() async {
  final headers = await getAuthenticatedHeaders();
  final response = await http.get(
    Uri.parse('https://api.spotify.com/v1/me/player/queue'),
    headers: headers,
  );

  if (response.statusCode != 200) {
    throw SpotifyAuthException('获取播放队列失败');
  }

  return json.decode(response.body);
}

建议

在 Flutter 中开发 Spotify 相关功能时,我建议:使用 Spotify SDK(一个 Flutter Package)处理认证流程,获取和保存访问令牌。直接调用 Spotify API 实现具体功能,而不是依赖官方 SDK。(根据实际需求选择必要的 API 端点)

RELATED POSTS

View all

view all