好久没写博客了,最近各种忙,大忙特忙,今晚难得有空,写个博客总结下最近完成的一个任务:使用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的音乐信息查询服务了吧。

使用GraceNote Web API开发Mac查询音乐信息应用相关推荐

  1. 使用GraceNote Web API发展Mac发现音乐信息的应用

    好久没有写博客,最近各种忙,特别忙里忙,今晚难得清闲.写最近完成下一个博客任务的摘要:使用GraceNote的Web API开发一个查询的音乐信息的应用,事实上,并在这些功能的前GraceNote S ...

  2. 使用SQL Server 2017 Docker容器在.NET Core中进行本地Web API开发

    目录 介绍 先决条件 最好事先知道 假设 动机 跨平台 快速安装 经济有效 不同版本/多个实例 速度 持久性 找到SQL Server 2017镜像并在本地下载它 在没有卷挂载的情况下在本地执行SQ​ ...

  3. 循序渐进学.Net Core Web Api开发系列【14】:异常处理

    循序渐进学.Net Core Web Api开发系列[14]:异常处理 参考文章: (1)循序渐进学.Net Core Web Api开发系列[14]:异常处理 (2)https://www.cnbl ...

  4. 循序渐进学.Net Core Web Api开发系列【7】:项目发布到CentOS7

    系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇讨论如 ...

  5. Web API 开发接口

    体验式API InfoQ上,Jérôme Louvel(Restlet联合创立者和首席geek)曾经采访过Daniel Jacobson(Netflix公司edge工程团队副主席),他们讨论了Netf ...

  6. 基于.Net Framework 4.0 Web API开发(4):ASP.NET Web APIs 基于令牌TOKEN验证的实现

    概述:  ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但是在使用API的时候总会遇到跨域请求的问题, ...

  7. Asp.net Web Api开发 性能:使用Jil提升Json序列化性能

    from:http://blog.csdn.net/sqqyq/article/details/51692342 看了几篇网上关于各种序列化工具的性能对比,在这里再粘贴下: 我们使用了ASP.NET ...

  8. Asp.net Web Api开发(第二篇)性能:使用Jil提升Json序列化性能

    看了几篇网上关于各种序列化工具的性能对比,在这里再粘贴下: 我们使用了ASP.NET WEB API来提供RESTfull风格的接口给APP调用,默认序列化库用的是:Newtonsoft.Json 为 ...

  9. Web API 开发入门--基于Visual Studio

    前言 ASP.NET Web API是​​一个框架,可以很容易构建达成了广泛的HTTP服务客户端,包括浏览器和移动设备.是构建RESTful应用程序的理想平台的.NET框架. 此处使用的Visual ...

最新文章

  1. 如何更快速加载你的JS页面
  2. 面试官:private修饰的方法可以通过反射访问,那么private的意义是什么?
  3. 三周写出高性能的Python代码,这些小技巧你值得一试。
  4. 微信小程序 --- 图片自适应、本地图片的使用
  5. 卡特兰数Catalan Number
  6. 第一次使用Winhex直接修改文件二进制数据
  7. IOS - 快速入门
  8. Opencv——批量处理同一文件夹下的图片(解决savedfilename = dest + filenames[i].substr(len)问题)
  9. c语言计算多个整数加减,求用C编个大数加减法运算程序
  10. PostgreSQL 设置单条SQL的执行超时 - 防雪崩
  11. oracle的order by排序中空字符串处理方法
  12. networkx 点的属性_在NetworkX中分配节点属性时发生类型错误
  13. 傻瓜方法求集合的全部子集问题(java版)
  14. Delphi XE生成UUID
  15. gitlab设置项目组成员权限
  16. 2022年危险化学品经营单位安全管理人员报名考试及危险化学品经营单位安全管理人员复审模拟考试
  17. 设计高效的交叉功率因数校正方案
  18. ibmr系列服务器怎么装架子,R440/R540/R640/R740 R820 R930 DELL服务器导轨 滑轨 支架 理线架...
  19. 关于PS CC 不能直接拖图片的问题
  20. LR(1)分析法的总控的实现(C++实现)

热门文章

  1. 面试必考排序算法最详细介绍,包含动画演示、大厂真题(每天一遍,面试必过)
  2. 期末课设—学生成绩管理系统的设计与实现—大作业
  3. uniapp微信小程序获取当前位置信息、经纬度转换、导航地图实现
  4. c语言json 5c,什么是json的转义字符
  5. XSS小游戏的通关之路
  6. windows下secureCRT远程登录virtualbox-ubu
  7. Linux下的Git三板斧
  8. 《数据库系统概念》学习笔记——第十章 存储和文件结构
  9. Python画图设置宋体和新罗马Times New Roman
  10. 工赋开发者社区 | 让小型企业提高 20 倍效率的统一技术栈