使用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消息进行解析并从中提取出我们想要的信息。下面是程序的一些常数:
?
其中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中:
?
所有的任务都可以通过NSURLSessionDataTask来完成。
然后根据艺术家名,专辑名,歌曲标题,搜索结果的返回范围来发起查询请求(album search):
?
将搜索到的gnID(在数据库中标识这个专辑的一个ID)保存进一个数组gn_IDs中,然后根据数组中的每个gn_id发起进一步的抓取专辑完整数据的操作(album fetch):
?
最后在NSTableView中将数据load出来:
?
另外我将专辑元数据抽象成了一个MFAlbum类,可以通过返回的XML响应数据初始化(在这里使用了GDataXML类库进行XML解析),代码如下:
?
主界面部分(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的官方资料:点击打开链接
首先看下基本的查询和响应的数据格式:
可以看到交互的形式是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 |
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]; } |
然后根据艺术家名,专辑名,歌曲标题,搜索结果的返回范围来发起查询请求(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]; } |
?
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]; } |
?
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; } } |
?
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; } |
<喎"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查询音乐信息应用
- 使用GraceNote Web API发展Mac发现音乐信息的应用
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-基础篇-演示ORM中的查询
- 使用jQuery mobile开发一个基于手机的在线餐馆订餐查询应用(第一部分)
- iPhone开发【十九】XML解析之NSXMLParser(使用Web Services查询火车信息)
- 使用SoundTouch开源库开发android手机上的音乐bpm检测应用
- web应用开发入门-使用mac版本eclipse搭建tomcat下web应用项目详细步骤
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-WinForm应用篇-演示使用报表构建UI-入库业务查询模块
- SQL应用与开发:(七)数据操作 · 查 · (三)使用子查询访问和修改数据
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-WinForm应用篇-库存查询模块
- 微信公众平台开发应用(天气预报、股票查询、手机归属地查询、在线听音乐、翻译、成绩查询等功能)代码分享如下
- 使用jQuery mobile开发一个基于手机的在线餐馆订餐查询应用
- 【原创】Android开发使用华为手机调试logcat没有应用输出信息
- SQL2000系统表、存储过程、函数的功能介绍及应用2009年01月21日 星期三 11:38虽然使用系统存储过程、系统函数与信息架构视图已经可以为我们提供了相当丰富的元数据信息,但是对于某些特殊的元数据信息,我们仍然需要直接对系统表进行查询。因为SQL
- 一步一步教你使用AgileEAS.NET基础类库进行应用开发-WinForm应用篇-演示使用报表构建UI-入库业务查询模块
- iPhone开发【十九】XML解析之NSXMLParser(使用Web Services查询火车信息)
- Mac下使用Phonegap(Apache Cordorva)开发iOS应用
- 如何在Mac上使用Swift调用C接口开发条形码应用
- 企业级搜索应用服务器Solr4.10.4部署开发详解(2)- Solr使用-创建集合表、存储、查询
- Mac下使用Phonegap(Apache Cordorva)开发iOS应用