好久没有写博客,最近各种忙,特别忙里忙,今晚难得清闲。写最近完成下一个博客任务的摘要:使用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的音乐信息查询服务了吧。
版权声明:本文博客原创文章。博客,未经同意,不得转载。