[原创] 在线音乐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

      

转载于:https://www.cnblogs.com/osmondy/p/LyricApi.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本来在做项目,看到酷我音乐盒的歌词显示挺有趣的,模仿做了一个不完整的。 (只有滚动显示,没有节奏显示)。 原理: (1)定义一个派生自CStatic类的CKaraokeLyricCtrl类(歌词控件),自绘制风格 ; (2)准备一个背景位图(保存在CKaraokeLyricCtrl::m_dcBK中); (3)设置两个计数器(ID分别为1和2),启动自绘制,1用来显示节奏(未实现,只 有框架),2用来滚动歌词; (4)自绘制函数中,将绘制的滚动歌词和背景位图混合,然后输出到屏幕上。滚动 歌词的绘制使用GDI+的Graphics::DrawString函数,歌词文本的大小、位置、字体和 透明度均自动计算和变化,模仿酷我音乐盒的形式。 以上功能均封闭实现在CKaraokeLyricCtrl类中。该类可以直接使用(见下边的使用步骤)。 使用步骤: (1)CKaraokeLyric::InitInstance中启动GDI+; (2)在CKaraokeLyricView::OnInitialUpdate中,创建歌词控件 (CKaraokeLyricCtrl类),其大小和CKaraokeLyricView视图相同,即覆盖了后者; (3)在菜单项响应中,使用CKaraokeLyricCtrl::ReadLyric读取歌词文件,再使用 CKaraokeLyricCtrl::Start即可启动歌词的滚动显示 未实现部分:(歌词的节拍显示) 虽然没有实现,但思路大致是:在后台先用另外一种颜色绘制当前突出显示的歌词(即字体最大的一行歌词),根据歌曲节奏,将还未唱出部分全部涂黑,然后和屏幕上的当前行突出歌词进行混合。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值