使用GraceNote Web API发展Mac发现音乐信息的应用
好久没有写博客,最近各种忙,特别忙里忙,今晚难得清闲。写最近完成下一个博客任务的摘要:使用GraceNote的Web API开发一个查询的音乐信息的应用,事实上,并在这些功能的前GraceNote SDK鲍文是一样的,次不使用不论什么SDK。单纯的使用Web API,然后开发的平台从iOS转移到了Mac上。于是,我人生中第一个Mac App Demo就出来了。
GraceNote Web API的官方资料:点击打开链接
首先看下主要的查询和响应的数据格式:
能够看到交互的形式是XML。
其实。不论什么调用GraceNote的Web API的消息,都是向一个指定的URL POST XML消息。然后对返回的XML消息进行解析并从中提取出我们想要的信息。以下是程序的一些常数:
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中:
// 向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):
// 以Artist,Album Title,Track Title为搜索keyword,发起搜索请求
- (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>\
<AUTH>\
<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>
<ALBUM ORD="1">
<GN_ID>7552265-4E82AF73CE400EDC94DCDA49547C585F</GN_ID>
<ARTIST>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_NUM>6</TRACK_NUM>
<GN_ID>7552271-366ED2D1FEB61E8D720D4941009C91A9</GN_ID>
<TITLE>Yesterday Once More</TITLE>
</TRACK>
</ALBUM>
<ALBUM ORD="2">
<GN_ID>19546461-AA0668FE5972459884664A7C3FE9D9C2</GN_ID>
<ARTIST>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_NUM>6</TRACK_NUM>
<GN_ID>19546467-560982E049BFF85016AB89C37513F474</GN_ID>
<TITLE>Yesterday Once More</TITLE>
</TRACK>
</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):
// 逐个抓取专辑的详细信息
- (void)albumFetch {
// 首先移除上次搜索的残留数据
[_searchAlbums removeAllObjects]; // 以gn_IDs中的每个gnID为搜索keyword。运行album fetch请求,抓取专辑的完整信息
for (NSString *gnID in _gn_IDs) {
[self gn_albumFetchWithGNID:gnID];
}
} // 以GN_ID为搜索keyword。运行album fetch请求,抓取专辑的完整信息
- (void)gn_albumFetchWithGNID:(NSString *)aID {
// 设置要查询的字符串,本次操作为ALBUM_FETCH操作
NSString *searchString = [NSString stringWithFormat:@"\
<QUERIES>\
<AUTH>\
<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出来:
#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解析),代码例如以下:
- (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):
最后上执行结果:
实在好久没写博客。写作水平下降得厉害,加上自己又变懒惰了非常多,这篇文章实在写得太烂,仅仅能当做做个记号,证明我有完毕了GraceNote的音乐信息查询服务了吧。
版权声明:本文博客原创文章。博客,未经同意,不得转载。
使用GraceNote Web API发展Mac发现音乐信息的应用的更多相关文章
- asp.net web api 向客户端返回错误信息
1使用Http状态码 ASP.NET Web Api框架提供了Http状态码的值,如下图所示. 虽然有这些预定义的状态码,但在实际项目中使用自定状态码结合预定义状态码更有优势. 通过在适当的位置抛出异 ...
- Gitlab CI 自动部署 asp.net core web api 到Docker容器
为什么要写这个? 在一个系统长大的过程中会经历不断重构升级来满足商业的需求,而一个严谨的商业系统需要高效.稳定.可扩展,有时候还不得不考虑成本的问题.我希望能找到比较完整的开源解决方案来解决持续集成. ...
- web api中的RouteHandler
ASP.NET MVC4中引入的Web API可以说是进行REST软件开发的利器(个人意见),但是最近在web form中混入web api时,发现一个问题:由于以前的web form项目中,使用到了 ...
- Asp.Net Web API 2第六课——Web API路由和动作选择
Asp.Net Web API 导航 Asp.Net Web API第一课——入门http://www.cnblogs.com/aehyok/p/3432158.html Asp.Net Web AP ...
- Web API路由和动作选择
前言 本文描述ASP.NET Web API如何把一个HTTP请求路由到控制器的一个特定的Action上.关于路由的总体概述可以参见上一篇教程 http://www.cnblogs.com/aehyo ...
- ASP.NET Web API 帮助(help)页面上没有 Test API按钮的解决方法
参与一个web API项目时发现它的help页面特别好用,不仅可以根据webapi的方法和注释自动生成帮助文档以方便查阅,还可以在这个页面上测试webapi方法.于是在自己新建项目时也打算将这个hel ...
- ASP.NET Web Api返回对象类型为JSON还是XML
在Umbraco平台上开发过程中,我用WebApi返回JSON result给前端 前端使用React调用这个web api来获取JSON result 我写的web api方法是返回JSON 类型的 ...
- Web API项目中使用Area对业务进行分类管理
在之前开发的很多Web API项目中,为了方便以及快速开发,往往把整个Web API的控制器放在基目录的Controllers目录中,但随着业务越来越复杂,这样Controllers目录中的文件就增加 ...
- 开发笔记:用Owin Host实现脱离IIS跑Web API单元测试
今天在开发一个ASP.NET Web API项目写单元测试时,实在无法忍受之前的笨方法,决定改过自新. 之前Web API的单元测试需要进行以下的操作: 初始配置: 1)在IIS中创建一个站点指定We ...
随机推荐
- QT---系统托盘图标不显示原因
很久没用QT写UI相关的东西了,有些东西都忘记了,今天竟然忘记了系统托盘图标是怎么显示的了.下面说下解决方法 1.现象, 设置了QSystemTrayIcon的Icon图标,但就是不显示自己设置的图片 ...
- linux(readhat) yum源安装
在安装測试环境的时候遇到了一个问题,/etc/yum/repos.d中不存在文件或目录,无法更新yum源. 解决方法: (一.配置网络yum源) 1.首先在/etc/yum/repos.d/文件夹下创 ...
- 人人网javascript面试题
JavaScript面试题要求:以下题目必须从一至四题中,选出三道题,使用原生代码实现,不可使用任何框架,第五题为选作题. 一. 在页面的固定区域内实现图片的展示 <ignore_ ...
- 【Demo 0009】Java基础-异常
本章学习要点: 1. 了解异常的基本概念: 2. 掌握异常捕获方法以及注意事项; 3. 掌握异常抛出方法: 4. 掌握自定义异常类和异常类继承注 ...
- POJ 1781 In Danger Joseph环 位运算解法
Joseph环,这次模固定是2.假设不是固定模2,那么一般时间效率是O(n).可是这次由于固定模2,那么能够利用2的特殊性,把时间效率提高到O(1). 规律能够看下图: watermark/2/tex ...
- Linux红黑树(二)——访问节点
核心对红黑树使用两点说明 1.头文件 <Documentation/rbtree.txt> Linux's rbtree implementation lives in the file ...
- MP3文件的结构与编程
有一个朋友喜欢听MP3,为了获取MP3,写了一个程序,专门从一家音乐网站上搜索下载mp3,一下子下载了有上千首.这时朋友又犯愁了,这些MP3的歌曲名字都是使用1,2,3,4,..等数字命名,挑选起来十 ...
- android电池充电以及电量检测驱动分析
前段时间比较烦躁,各种不想学习不想工作,于是休息了几天.这几天又下来任务了--调试充电电路和电池电量检测电路,于是又开始工作,顺便把调试过程记录下来. 平台: cpu 飞思卡尔imx6q ...
- Oracle 10g AND Oracle 11g手工建库案例--Oracle 11g
Oracle 10g AND Oracle 11g手工建库案例--Oracle 11g 系统环境: 操作系统: RedHat EL6 Oracle: Oracle 10g and Oracle 11 ...
- POJ2421 & HDU1102 Constructing Roads(最小生成树)
嘎唔!~又一次POJ过了HDU错了...不禁让我想起前两天的的Is it a tree? orz..这次竟然错在HDU一定要是多组数据输入输出!(无力吐槽TT)..题目很简单,炒鸡水! 题意: 告 ...