我是Flutter和使用API的新手。我正在尝试做一个非常简单的Flutter应用程序,它会向用户推荐与他们使用www.example.com API输入的音乐曲目相似的曲目Last.fm。我已经有了一个具有API密钥和共享密钥的帐户。但是,我不确定如何实现Last.fm API。每次我尝试获得推荐时,什么都没有发生,我收到错误消息:"flutter: Failed to authenticate or get similar tracks: Connection failed."
我知道它与URL有关,因为当我单击'http://ws.audioscrobbler.com/2.0/'
链接时,文档树看起来是这样的:
<lfm status="failed">
<style class="darkreader darkreader--safari-fallback">
html, body, body > :not(iframe) { background-color: #181a1b !important; border-color: #776e62 !important; color: #e8e6e3 !important; }
</style>
<error code="6">
Invalid parameters - Your request is missing a required parameter
</error>
</lfm>
下面是主要的.dart代码:
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MyApp());
}
class LastfmApi {
final String baseUrl = 'http://ws.audioscrobbler.com/2.0/';
final String apiKey = '31010505f53e40c5c06cbf0941e5f380';
final String secret = 'f0288f77e13a6c6fa1d5a5622a1a6f79';
String? sessionKey;
String _generateSignature(Map<String, String> params) {
final sortedParams = Map.fromEntries(
params.entries.toList()..sort((a, b) => a.key.compareTo(b.key)));
final paramStrings =
sortedParams.entries.map((e) => '${e.key}${e.value}').join('');
final signature = utf8.encode(secret + paramStrings);
return md5.convert(signature).toString();
}
Future<void> authenticate(String username, String password) async {
final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).round();
final params = {
'api_key': apiKey,
'method': 'auth.getMobileSession',
'password': password,
'username': username,
'timestamp': timestamp.toString(),
};
params['api_sig'] = _generateSignature(params);
final response = await http.post(Uri.parse(baseUrl), body: params);
if (response.statusCode == 200) {
final jsonBody = jsonDecode(response.body);
final session = jsonBody['session'];
if (session != null) {
sessionKey = session['key'];
} else {
throw Exception('Failed to authenticate');
}
} else {
throw Exception('Failed to authenticate');
}
}
Future<List<String>> getSimilarTracks(String track, String artist) async {
if (sessionKey == null) {
throw Exception('Not authenticated');
}
final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).round();
final params = {
'api_key': apiKey,
'format': 'json',
'limit': '10',
'sk': sessionKey!,
'method': 'track.getsimilar',
'timestamp': timestamp.toString(),
'track': track,
'artist': artist,
'autocorrect': '1',
};
params['api_sig'] = _generateSignature(params);
final response = await http.get(Uri.parse(baseUrl +
'?' +
params.entries
.map((e) => '${e.key}=${Uri.encodeComponent(e.value)}')
.join('&')));
if (response.statusCode == 200) {
final jsonBody = jsonDecode(response.body);
if (jsonBody.containsKey('error')) {
throw Exception(jsonBody['message']);
}
final tracks = jsonBody['similartracks']['track'] as List<dynamic>;
final trackNames =
tracks.map((track) => track['name'] as String).toList();
return trackNames;
} else {
throw Exception('Failed to get similar tracks');
}
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Last.fm Music Recommendations',
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
),
darkTheme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
final TextEditingController _trackController = TextEditingController();
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final LastfmApi _lastfmApi = LastfmApi();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Title for recommendation'),
),
body: Column(
children: [
TextField(
controller: _trackController,
decoration: InputDecoration(
labelText: 'Enter a track name',
),
),
TextField(
controller: _usernameController,
decoration: InputDecoration(
labelText: 'Enter your Last.fm username',
),
),
TextField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: 'Enter your Last.fm password',
),
),
ElevatedButton(
onPressed: () async {
try {
await _lastfmApi.authenticate(
_usernameController.text, _passwordController.text);
final recommendations = await _lastfmApi.getSimilarTracks(
_trackController.text, '');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RecommendationPage(
track: _trackController.text,
recommendations: recommendations),
),
);
} catch (e) {
print('Failed to authenticate or get similar tracks: $e');
}
},
child: Text('Get recommendations'),
),
],
),
);
}
}
class RecommendationPage extends StatefulWidget {
final String track;
final List<dynamic> recommendations;
RecommendationPage({required this.track, required this.recommendations});
@override
_RecommendationPageState createState() => _RecommendationPageState();
}
class _RecommendationPageState extends State<RecommendationPage> {
@override
void initState() {
super.initState();
}
/*
Future<void> _loadRecommendations() async {
try {
final recommendations = await getSimilarTracks(widget.track, '');
setState(() {
_recommendations = recommendations;
});
} catch (e) {
throw Exception('Failed to load recommendations');
}
}
*/
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Recommendations for ${widget.track}'),
),
body: ListView.builder(
itemCount: widget.recommendations.length,
itemBuilder: (BuildContext context, int index) {
final recommendation = widget.recommendations[index];
return ListTile(
leading: Image.network(recommendation['image'][2]['#text']),
title: Text(recommendation['name']),
subtitle: Text(recommendation['artist']['name']),
);
},
),
);
}
}
我知道说明在这个链接:https://www.last.fm/api/mobileauth,但我对如何实现它感到困惑。
我尝试更改代码,以便它需要last.fm用户名和密码,并使用三个TextEditingController对象_trackController、_usernameController和_passwordController,它们用于捕获用户输入的曲目名称、用户名和密码。我以为它会验证Last.fm API并获得建议。但是,当我点击“获取建议”按钮时,什么也没有发生。
1条答案
按热度按时间bprjcwpo1#
我还没有检查你所有的代码,但我已经看到了一些部分,应该值得尝试以另一种方式做,根据文档。
1.你有一个API的基本URL,但是当你调用某个方法时,你也需要将该路径附加到URL中。例如:
http://ws.audioscrobbler.com/2.0/track.getSimilar
。1.端点方法区分大小写,例如
track.getsimilar
!=track.getSimilar
1.请注意,为了验证移动的会话,您需要使用API url的
https
变体,如in the documentation所述。这是因为您通过线路发送用户的密码。1.据我所知,您不需要
timestamp
作为身份验证请求中的参数。1.在生成
api_sig
(_generateSignature
)的方法中,您将secret作为字符串的前缀,但应该将其追加:final signature = utf8.encode(paramStrings + secret);
以下是一些改进代码的提示。让我知道这是否有帮助,或者是否出现了新的问题。