您的位置:首页 > 其它

使用GraceNote Web API开发Mac查询音乐信息应用

2015-01-22 09:44 423 查看
好久没写博客了,最近各种忙,大忙特忙,今晚难得有空,写个博客总结下最近完成的一个任务:使用GraceNote的Web API来开发一个查询音乐信息的应用,其实功能和前面的那些GraceNote SDK的博文是一样的,只是这一次不使用任何SDK,单纯的使用Web API,然后开发的平台从iOS转移到了Mac上,于是,我人生中第一个Mac App Demo就出来了。
GraceNote Web API的官方资料:点击打开链接

首先看下基本的查询和响应的数据格式:

可以看到交互的形式是XML。
事实上,任何调用GraceNote的Web API的消息,都是向一个指定的URL POST XML消息,然后对返回的XML消息进行解析并从中提取出我们想要的信息。下面是程序的一些常数:

?
1
2
3
NSString * const
kWebAPIURL = @"https://c10239232.web.cddbp.net/webapi/xml/1.0/"; // 调用网络接口的URL
NSString * const
kClientID = @"10239232";
// 你申请的应用的Client ID
NSString * const
kClientTag = @"46B9ABAD30F0F5EB409C7BFAA13EB2EF";
// 你申请的应用的Client Tag
其中kWebAPIURL就是这个固定的发起请求的URL,注意将c后面的数字替换成你的App的Client ID。
kClient ID和kClient Tag可以从在网站中注册的App中找到。

在使用GraceNote的Web API进行查询之前,首先要通过App的Client ID和Client Tag来注册一个User ID,然后在所有后续查询中都要使用这个User ID和之前的Client ID来进行认证,格式如下:
首先看看注册的代码,在注册成功后我们将其保存到本地的NSUserDefaults中:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 向GraceNote网站注册User ID
- (void)gn_registerUserID {
NSString *registerString = [NSString stringWithFormat:@"\
<queries>\
<query cmd="\"REGISTER\"">\
<client>%@-%@</client>\
</query>\
</queries>",
kClientID, kClientTag];
// 要POST的字符串,CMD=REGISTER表示注册动作
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:kWebAPIURL]];
[request setHTTPMethod:@"POST"];
NSData *data = [registerString dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:data];

// 建立NSURLSessionDataTask
NSURLSession *session = [NSURLSession sharedSession];
__weak AppDelegate *weakSelf = self;
// 防止self和block形成retain cycle
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"*** Register ***");
[self showResponseCode:response];

if
(data) {
NSError *parseError = nil;
// 这里使用第三方类库GDataXML解析XML数据,请确保已经安装GDataXML类库
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:data encoding:NSUTF8StringEncoding error:&parseError];
if
(parseError) {
NSLog(@"Parse Error:%@", [parseError localizedDescription]);
weakSelf.app_userID = nil;
}
else
{
/**
* 返回的XML数据示例:
<responses>
<response status="OK">
<user>267493051066226693-31C70A189A61B89C0D45A782DCB7C072</user>
</response>
</responses>
*/
GDataXMLElement *rootElement = [doc rootElement];
NSArray *responses = [rootElement elementsForName:kGNResponse];
GDataXMLElement *resp = responses[0];
if
(![self gn_requestSucceed:resp]) {
return;
}

NSString *userID = [[resp elementsForName:kGNUser][0] stringValue];

// 将获取到的user id保存起来
weakSelf.app_userID = userID;

// 将user id存储到User Defaults中
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:userID forKey:kUserID];
[userDefaults synchronize];

NSLog(@"User ID = %@", userID);
}
}

if
(error) {
NSLog(@"error : %@", [error localizedDescription]);
}

NSLog(@"--- Register Finished ---");
}];

// 最后一定要用resume方法启动任务
[dataTask resume];
}
所有的任务都可以通过NSURLSessionDataTask来完成。

然后根据艺术家名,专辑名,歌曲标题,搜索结果的返回范围来发起查询请求(album search):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 以Artist,Album Title,Track Title为搜索关键字,发起搜索请求
- (void)gn_albumSearchWithArtist:(NSString *)anArtist
albumTitle:(NSString *)anAlbumTitle
trackTitle:(NSString *)aTrackTitle
start:(NSUInteger)startIndex
end:(NSUInteger)endIndex
{
// 首先移除上次残留的查询结果
[_gn_IDs removeAllObjects];

if
(startIndex <= 0
|| endIndex <= 0
|| startIndex > endIndex) {
return;
}

// 设置查询字符串,本次请求属于ALBUM_SEARCH操作
NSString *searchString = [NSString stringWithFormat:@"\
<queries>\
\
<client>%@-%@</client>\
<user>%@</user>\
</auth>\
<query cmd="\"ALBUM_SEARCH\"">\
<text type="\"ARTIST\"">%@</text>\
<text type="\"ALBUM_TITLE\"">%@</text>\
<text type="\"TRACK_TITLE\"">%@</text>\
<range>\
<start>%ld</start>\
<end>%ld</end>\
</range>\
</query>\
</queries>",
kClientID, kClientTag, _app_userID, anArtist, anAlbumTitle, aTrackTitle, startIndex, endIndex];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:kWebAPIURL]];
[request setHTTPMethod:@"POST"];
NSData *data = [searchString dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:data];

// 建立NSURLSessionDataTask并用resume方法启动任务
NSURLSession *session = [NSURLSession sharedSession];
__weak AppDelegate *weakSelf = self;
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"*** Album Search ***");
[self showResponseCode:response];

if
(data) {
NSError *parseError = nil;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:data encoding:NSUTF8StringEncoding error:&parseError];
if
(parseError) {
NSLog(@"Parse Error:%@", [parseError localizedDescription]);
}
else
{
/**
* 请求成功,返回XML结果示例:
<responses>
<response status="OK">
<range>
<count>2</count>
<start>1</start>
<end>2</end>
</range>

<gn_id>7552265-4E82AF73CE400EDC94DCDA49547C585F</gn_id>
The Carpenters</artist>
<title>Now & Then</title>
<pkg_lang>ENG</pkg_lang>
<date>1973</date>
<genre num="61365" id="25333">70's Rock</genre>
<matched_track_num>6</matched_track_num>
<track_count>15</track_count>
<track></track>
<track_num>6</track_num>
<gn_id>7552271-366ED2D1FEB61E8D720D4941009C91A9</gn_id>
<title>Yesterday Once More</title>

</album>

<gn_id>19546461-AA0668FE5972459884664A7C3FE9D9C2</gn_id>
The Carpenters</artist>
<title>Now And Then</title>
<pkg_lang>ENG</pkg_lang>
<genre num="61365" id="25333">70's Rock</genre>
<matched_track_num>6</matched_track_num>
<track_count>8</track_count>
<track></track>
<track_num>6</track_num>
<gn_id>19546467-560982E049BFF85016AB89C37513F474</gn_id>
<title>Yesterday Once More</title>

</album>
</response>
</responses>
*/
GDataXMLElement *rootElement = [doc rootElement];
NSArray *responses = [rootElement elementsForName:kGNResponse];
if
([responses count]) {
GDataXMLElement *resp = [responses firstObject];
if
(![self gn_requestSucceed:resp]) {
return;
}

GDataXMLElement *range = [resp elementsForName:kGNRange][0];
if
(!range) { // 如果没有返回range元素,那么抓取数据失败
NSLog(@"Fail to search album");
return;
}
NSUInteger count = (NSUInteger)[[[range elementsForName:kGNCount][0] stringValue] integerValue];
NSUInteger start = (NSUInteger)[[[range elementsForName:kGNStart][0] stringValue] integerValue];

if
(count <= 0) {
// 没有搜索到结果,直接返回
[self showSearchResultsCountText:0];
return;
}

p_currentPage = start /
10 +
1;
p_allPages = count /
10;
NSUInteger i = (count - count /
10 *
10) ? 1
: 0;
p_allPages += i;
[self updatePagingText];
[self showSearchResultsCountText:count];

NSUInteger searchCount =
0;
if
(endIndex >= count) {
searchCount = count - startIndex;
}
else
{
searchCount = endIndex - startIndex;
}

NSArray *albums = [resp elementsForName:kGNAlbum];
for
(NSUInteger i = 0; i <= searchCount; i++) {
GDataXMLElement *album = albums[i];
NSString *gn_id = [[album elementsForName:kGNID][0] stringValue];

// 将每一条搜索结果的GN_ID添加到数组gn_IDs中
[weakSelf.gn_IDs addObject:gn_id];
}

[_previousPage_button setEnabled:YES];
[_nextPage_button setEnabled:YES];

// 逐个抓取专辑的具体信息
[weakSelf albumFetch];
}
}
}

if
(error) {
NSLog(@"error : %@", [error localizedDescription]);
}

NSLog(@"--- Album Search Finished ---");
}];

[dataTask resume];
}
将搜索到的gnID(在数据库中标识这个专辑的一个ID)保存进一个数组gn_IDs中,然后根据数组中的每个gn_id发起进一步的抓取专辑完整数据的操作(album fetch):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 逐个抓取专辑的具体信息
- (void)albumFetch {
// 首先移除上次搜索的残留数据
[_searchAlbums removeAllObjects];

// 以gn_IDs中的每一个gnID为搜索关键字,执行album fetch请求,抓取专辑的完整信息
for
(NSString *gnID in _gn_IDs) {
[self gn_albumFetchWithGNID:gnID];
}
}

// 以GN_ID为搜索关键字,执行album fetch请求,抓取专辑的完整信息
- (void)gn_albumFetchWithGNID:(NSString *)aID {
// 设置要查询的字符串,本次操作为ALBUM_FETCH操作
NSString *searchString = [NSString stringWithFormat:@"\
<queries>\
\
<client>%@-%@</client>\
<user>%@</user>\
</auth>\
<query cmd="\"ALBUM_FETCH\"">\
<mode>SINGLE_BEST_COVER</mode>\
<gn_id>%@</gn_id>\
<option>\
<parameter>SELECT_EXTENDED</parameter>\
<value>COVER,ARTIST_IMAGE</value>\
</option>\
<option>\
<parameter>COVER_SIZE</parameter>\
<value>THUMBNAIL</value>\
</option>\
</query>\
</queries>",
kClientID, kClientTag, _app_userID, aID];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:kWebAPIURL]];
[request setHTTPMethod:@"POST"];
NSData *data = [searchString dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:data];

// 建立NSURLSessionDataTask并用resume方法启动任务
NSURLSession *session = [NSURLSession sharedSession];
__weak AppDelegate *weakSelf = self;
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"*** Album Fetch ***");
[self showResponseCode:response];

if
(data) {
// // 输出返回的xml内容
// [self logoutXMLData:data];

// 通过返回的xml二进制数据初始化MFAlbum对象
MFAlbum *album = [[MFAlbum alloc] initWithXMLData:data];
if
(album) {
// 将查询结果添加到searchAlbums数组中
[weakSelf.searchAlbums addObject:album];
}

[weakSelf showResults];
}

if
(error) {
NSLog(@"error : %@", [error localizedDescription]);
}

NSLog(@"--- Album Fetch Finished ---");
}];

[dataTask resume];
}
最后在NSTableView中将数据load出来:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#pragma mark - NSTableViewDataSource

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return
[_searchAlbums count];
}

- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSString *unknown = @"未知";
MFAlbum *album = _searchAlbums[row];
NSString *identifier = tableColumn.identifier;
if
([identifier isEqualToString:@"coverArt"]) {
NSURL *coverArtURL = [NSURL URLWithString:album.coverArtURLString];
NSImage *image;
if
(coverArtURL) {
image = [[NSImage alloc] initWithContentsOfURL:coverArtURL];
}
else
{
image = [NSImage imageNamed:@"NotFound"];
}
return
image;
}
else
if ([identifier isEqualToString:@"artistImage"]) {
NSURL *artistImageURL = [NSURL URLWithString:album.artistImageURLString];
NSImage *image;
if
(artistImageURL) {
image = [[NSImage alloc] initWithContentsOfURL:artistImageURL];
}
else
{
image = [NSImage imageNamed:@"NotFound"];
}

return
image;
}
else
if ([identifier isEqualToString:@"trackCount"]) {
return
[NSString stringWithFormat:@"%ld", album.trackCount] ? [NSString stringWithFormat:@"%ld", album.trackCount] : unknown;
}
else
{
NSString *info = [album valueForKey:identifier];
return
info ? info : unknown;
}
}
另外我将专辑元数据抽象成了一个MFAlbum类,可以通过返回的XML响应数据初始化(在这里使用了GDataXML类库进行XML解析),代码如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
- (instancetype)initWithXMLData:(NSData *)xmlData {
self = [super
init];
if
(self) {
NSError *parseError = nil;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData encoding:NSUTF8StringEncoding error:&parseError];
if
(parseError) {
NSLog(@"Parse Error:%@", [parseError localizedDescription]);
return
nil; // 转换出错,直接返回nil
}

// 逐个解析xml结点,获取专辑对象所需要的所有信息
GDataXMLElement *rootElement = [doc rootElement];
GDataXMLElement *response = [rootElement elementsForName:kGNResponse][0];
if
(![self gn_requestSucceed:response]) {
return
nil;
}

GDataXMLElement *album = [response elementsForName:kGNAlbum][0];
_gn_id = [[album elementsForName:kGNID][0] stringValue];
_artistName = [[album elementsForName:kGNArtist][0] stringValue];
_albumTitle = [[album elementsForName:kGNTitle][0] stringValue];
_language = [[album elementsForName:kGNLanguage][0] stringValue];
_releaseDate = [[album elementsForName:kGNDate][0] stringValue];
_genre = [[album elementsForName:kGNGenre][0] stringValue];
_trackCount = (NSUInteger)[[[album elementsForName:kGNTrackCount][0] stringValue] integerValue];

_allTracks = [NSMutableArray array];
NSArray *tracks = [album elementsForName:kGNTrack];
for
(GDataXMLElement *trackElement in tracks) {
NSString *title = [[trackElement elementsForName:kGNTitle][0] stringValue];
[_allTracks addObject:title];
}

NSArray *urlElements = [album elementsForName:kGNURL];
if
(!urlElements) {
return
self;
}
for
(GDataXMLElement *element in urlElements) {
GDataXMLNode *node = [element attributeForName:kGNType];
NSString *type = [node stringValue];
if
([type isEqualToString:kGNCoverArt]) {
_coverArtURLString = [element stringValue];
}
else
if ([type isEqualToString:kGNArtistImage]) {
_artistImageURLString = [element stringValue];
}
}
}
return
self;
}
主界面部分(MainMenu.xib):

<喎"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+1+6688nP1MvQ0L3hufujujwvcD4KPHA+PGltZyBzcmM9"http://www.2cto.com/uploadfile/Collfiles/20140610/201406100918356.png" alt="">

实在好久没写博客,写作水平下降得厉害,加上自己又变懒惰了很多,这篇文章实在写得太烂,只能当做做个记号,证明我有完成了GraceNote的音乐信息查询服务了吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐