[原創] 在線音樂API的研究 (Part 2.1)


      最近,在優化一個自己寫的音樂播放器。主要目的是回顧、歸納,並希望能夠寫出一個屬於自己的common lib。今天,主要是關於在線音樂API的一些分析結果。此次,主要分析的是歌詞、專輯部分。在線搜索音樂、熱門音樂及mp3的下載等,會在PART 2.2進行補充。

      原始API來源於網絡資料,部分是后面使用個人補充的。主要包括百度API、騰訊API及歌詞迷API,其中只有歌詞迷的API是官方正式發布的。三個API都有着各自的優點、缺點,如下:

      (1) 百度API,請求方式穩定,速度快,資源最多,獲取歌詞比較准確;但是數據結構相對繁雜些,每行的歌詞長度差異比較大。

      (2) 騰訊API,請求方式相對穩定,速度快,資源較多,准確度高,每行的歌詞長度相當;但JSON(Xml相對正常)數據結構並不完全標准,解析麻煩, 專輯圖片封面(約50KB|500 x 500 像素)較大。

      (3)歌詞迷API,有官方正式API,使用簡單,專輯封面相對小些(約10KB|185 x 160 像素);遺憾的是資源相對少,尤其在最新的資源方面,有點慢。

      提醒:以上全是個人開發的總結,並沒有完整體系性的驗證。

      如專輯封面大小問題,視乎個人開發需要而定,如果需要大圖片,騰訊的保真度高,如果需要小圖片,無疑歌詞迷更好些。

      本人在歌詞方面使用的騰訊API,專輯封面使用的是歌詞迷API。 


       整個實現思路比較明確,大體上的類圖設計如下:

Osmondy Lyric

      直接使用LyricLoader的loadLyric()方法進行歌詞下載,loadLyric()方法封裝了具體的處理邏輯,具體實現下載,由子類實現IDownload<Lyric>接口。摘取部分代碼:

/**
 * 歌詞助手
 * 
 * @author Osmondy
 * 
 */
public abstract class LyricLoader implements IDownload<Lyric>
{
    
    public LyricLoader(String name)
    {
        
    }
    
    /**
     * 獲取網絡請求歌詞地址
     * 
     * @param music
     * @return
     */
    public abstract String getServerLyricUrl(Music music);
    
    /**
     * 返回本地存儲歌詞的路徑
     * 
     * @param music
     * @return
     */
    protected String getLocalLyricPath(String songname, String singername)
    {
        
    }
    
    /**
     * 返回歌詞, Step1: 本地歌詞目錄加載; Step2: 網絡下載.
     * 
     * @param music
     * @return
     */
    public Lyric loadLyric(Music music)
    {
        if (TextUtils.isEmpty(music.getArtist()) || TextUtils.isEmpty(music.getTitle()))
        {
            Log.W(TAG, "Empty aritst or title, can't find lyric.");
            
            return null;
        }
        
        Lyric lyric = null;
        String localPath = getLocalLyricPath(music.getTitle(), music.getArtist());
        
        File file = new File(localPath);
        if (file.exists())
        {
            // 本地存在歌詞文件, 直接加載此歌詞.
            Log.D(TAG, "Loading lyric from local path.");
            try
            {
                lyric = loadLocalLyric(localPath);
                if (lyric != null)
                {
                    lyric.setSongname(music.getTitle());
                    lyric.setSingername(music.getArtist());
                    
                    Log.I(TAG, "Load local lyric finished. Lyric: " + lyric);
                }
            }
            catch (IOException e)
            {
                if (e instanceof FileNotFoundException)
                {
                    Log.W(TAG, "Lyric not found.");
                }
                else
                {
                    e.printStackTrace();
                }
            }
            
            return lyric;
        }
        
        String requestUrl = getServerLyricUrl(music);
        
        if (!TextUtils.isEmpty(requestUrl))
        {
            Log.D(TAG, "---------- Download lyric start ----------");
            try
            {
                lyric = download(requestUrl, localPath);
            }
            catch (HttpRequestException e)
            {
                e.printStackTrace();
            }
            
            Log.D(TAG, "---------- Download lyric end ----------");
            
            return lyric;
        }
        
        Log.W(TAG, "Not found a correct server lyric path.");
        
        return null;
        
    }
    
    /**
     * 保存歌曲文件, 默認保存至{@link AppConfig#DIRECTORY_LYRIC}, 子類可自行重寫保存至其它路徑. 保存時,
     * 先保存成*.lrc.tmp, 下載及保存成功后, 再重命名為*.lrc. 防止異常或停止下載歌詞, 下次無法再次下載.
     * 
     * @param is
     * @param music
     * @return
     */
    protected boolean saveLyric(InputStream is, String savePath)
    {
        
    }
    
    /**
     * 返回指定地址的歌詞文件
     * 
     * @param path
     * @return
     * @throws IOException
     */
    public Lyric loadLocalLyric(String path) throws IOException
    {
        
    }
    
}

     抽象類LyricLoader提供了對歌詞保存、加載的默認處理方式,子類可以自行重寫saveLyric()、loadLocalLyric()定義自己的處理方式。子類的實現以百度API為例,它使用的是父類LyricLoader提供的默認實現。

/**
 * 歌詞來源於Baidu
 * 
 * @author Osmondy
 * 
 */
public class BaiduLyricHelper extends LyricLoader
{
    
    private static final String TAG = "BaiduLyricHelper";
    
    /**
     * 歌曲信息請求地址
     */
    protected static final String SONGINFO_BASE_URL = "http://box.zhangmen.baidu.com/x";
    
    /**
     * 歌詞文件請求地址
     */
    protected static final String LYRIC_BASE_URL = "http://box.zhangmen.baidu.com/bdlrc";
    
    public BaiduLyricHelper()
    {
        super("BaiDu");
    }
    
    @Override
    public Lyric download(String requestUrl, String savePath) throws HttpRequestException
    {
        
    }
    
    @Override
    public String getServerLyricUrl(Music music)
    {
        
    }
    
}

     比較完整的代碼已經上傳至github:https://github.com/osmondy/LyricApi

 


      原始API如下:

      (1) 百度API

      歌曲信息請求地址:http://box.zhangmen.baidu.com/x?op=12&count=1&title=歌詞名稱$$歌手名稱$$$$

      歌詞信息請求地址:http://box.zhangmen.baidu.com/bdlrc/歌詞ID除以100/歌詞ID.lrc

/**
     * 返回請求歌詞的地址, 通過 SongInfo生成最終可請求到歌詞文件的地址. </br>
     * 
     * @param songInfo
     * @return
     */
    protected String getServerLyricUrlBySongInfo(SongInfo songInfo)
    {
        int lrcid = songInfo.getLrcid();
        int postfix = lrcid / 100;
        
        StringBuffer sb = new StringBuffer();
        sb.append(LYRIC_BASE_URL);
        sb.append("/");
        sb.append(postfix);
        sb.append("/");
        sb.append(lrcid);
        sb.append(".lrc");
        
        return sb.toString();
    }

    @Override
    public String getServerLyricUrl(Music music)
    {
        if (TextUtils.isEmpty(music.getTitle()) || TextUtils.isEmpty(music.getArtist()))
        {
            return null;
        }
        //protected static final String SONGINFO_BASE_URL = "http://box.zhangmen.baidu.com/x?op=12&count=1&title=";
        Log.D(TAG, "Songname: " + music.getTitle() + ", Singername: " + music.getArtist());
        StringBuffer sb = new StringBuffer();
        try
        {
            sb.append(SONGINFO_BASE_URL);
            sb.append("?");
            sb.append("op=12");
            sb.append("&");
            sb.append("count=1");
            sb.append("&");
            sb.append("title=");
            sb.append(URLEncoder.encode(music.getTitle(), "utf-8"));
            sb.append("$$");
            sb.append(URLEncoder.encode(music.getArtist(), "utf-8"));
            sb.append("$$$$");
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }
        
        return sb.toString();
    }
構建請求的URL

 

      (2) 騰訊API

      編碼並非是UTF-8,而是GBK(gb2312)。

      歌曲信息請求地址:http://qqmusic.qq.com/fcgi-bin/qm_getLyricId.fcg?name=連哭都是我的錯&singer=東來東往&from=qqplayer

      歌詞請求地址:http://music.qq.com/miniportal/static/lyric/歌曲ID求余100/歌曲ID.xml

      專輯封面請求地址:http://imgcache.qq.com/music/photo/album/專輯ID求余100/albumpic_專輯ID_0.jpg

/**
     * 返回請求歌詞的地址, 通過 SongInfo生成最終可請求到歌詞文件的地址. </br>
     * 請求地址格式: http://music.qq.com/miniportal/static/lyric/67/183767.xml
     * 
     * @param songInfo
     * @return
     */
    protected String getServerLyricUrlBySongInfo(SongInfo songInfo)
    {
        String id = songInfo.getId();
        
        if (!StringUtils.isNumeric(id))
        {
            return null;
        }
        int postfix = Integer.parseInt(id) % 100;
        
        StringBuffer sb = new StringBuffer();
        sb.append(LYRIC_BASE_URL);
        sb.append("/");
        sb.append(postfix);
        sb.append("/");
        sb.append(id);
        sb.append(".xml");
        
        return sb.toString();
    }
    

    /**
     * 返回請求歌曲信息的地址.
     * 請求地址格式: http://qqmusic.qq.com/fcgi-bin/qm_getLyricId.fcg?name=連哭都是我的錯&singer=東來東往&from=qqplayer
     */
    @Override
    public String getServerLyricUrl(Music music)
    {
        if (TextUtils.isEmpty(music.getTitle()) || TextUtils.isEmpty(music.getArtist()))
        {
            return null;
        }
        
        Log.D(TAG, "Songname: " + music.getTitle() + ", Singername: " + music.getArtist());
        StringBuffer sb = new StringBuffer();
        try
        {
            sb.append(SONGINFO_BASE_URL);
            sb.append("?");
            sb.append("name=" + URLEncoder.encode(music.getTitle(), "gbk"));
            sb.append("&");
            sb.append("singer=" + URLEncoder.encode(music.getArtist(), "gbk"));
            sb.append("&");
            sb.append("from=qqplayer");
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }
        
        return sb.toString();
    }
構建請求的URL

 

      (3)歌詞迷API

      直接提供官方地址:http://api.geci.me/en/latest/

      歌詞請求地址:http://geci.me/api/lyric/:歌曲名/:歌手名

      專輯封面請求地址:http://geci.me/api/cover/:專輯ID

@Override
    public String getServerLyricUrl(Music music)
    {
        if (TextUtils.isEmpty(music.getTitle()) || TextUtils.isEmpty(music.getArtist()))
        {
            return null;
        }
        
        Log.D(TAG, "Songname: " + music.getTitle() + ", Singername: " + music.getArtist());
        StringBuffer sb = new StringBuffer();
        try
        {
            sb.append(LYRIC_BASE_PATH);
            sb.append("/");
            sb.append(URLEncoder.encode(music.getTitle(), "utf-8"));
            sb.append("/");
            sb.append(URLEncoder.encode(music.getArtist(), "utf-8"));
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }
        
        return sb.toString();
    }
    
    /**
     * 返回歌曲專輯信息請求地址
     * 
     * @param albumId
     * @return
     */
    public String getServerAlbumUrl(String albumId)
    {
        return ALBUM_BASE_PATH + "/" + albumId;
    }
構建請求的URL

      

      最后,附上騰訊如何獲取新歌榜及總榜的請求。

      新歌榜:http://music.qq.com/musicbox/shop/v3/data/hit/hit_newsong.js
      總榜:http://music.qq.com/musicbox/shop/v3/data/hit/hit_all.js

      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM