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

来源:互联网 发布:linux 禁止端口访问 编辑:程序博客网 时间:2024/06/04 17:54

好久没写博客了,最近各种忙,大忙特忙,今晚难得有空,写个博客总结下最近完成的一个任务:使用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/"; // 调用网络接口的URLNSString * const kClientID  = @"10239232"; // 你申请的应用的Client IDNSString * 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为搜索关键字,发起搜索请求- (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为搜索关键字,执行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>\                                <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的音乐信息查询服务了吧。





1 1
原创粉丝点击