zoukankan      html  css  js  c++  java
  • 使用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的音乐信息查询服务了吧。





    版权声明:本文博客原创文章。博客,未经同意,不得转载。

  • 相关阅读:
    English 2
    速算24点
    心理学1
    从微服务到函数式编程
    034 01 Android 零基础入门 01 Java基础语法 04 Java流程控制之选择结构 01 流程控制概述
    033 01 Android 零基础入门 01 Java基础语法 03 Java运算符 13 运算符和表达式知识点总结
    032 01 Android 零基础入门 01 Java基础语法 03 Java运算符 12 运算符和if-else条件语句的综合案例——闰年问题
    031 01 Android 零基础入门 01 Java基础语法 03 Java运算符 11 运算符的优先级
    030 01 Android 零基础入门 01 Java基础语法 03 Java运算符 10 条件运算符
    029 01 Android 零基础入门 01 Java基础语法 03 Java运算符 09 逻辑“非”运算符
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/4643520.html
Copyright © 2011-2022 走看看