最近,在优化一个自己写的音乐播放器。主要目的是回顾、归纳,并希望能够写出一个属于自己的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。


整个实现思路比较明确,大体上的类图设计如下:

直接使用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 / ; 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) % ; 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

[原创] 在线音乐API的研究 (Part 2.1)的更多相关文章

  1. 在线音乐API的研究 (Part 2.1)

    本文转载于:http://www.cnblogs.com/osmondy/p/LyricApi.html 最近,在优化一个自己写的音乐播放器.主要目的是回顾.归纳,并希望能够写出一个属于自己的comm ...

  2. 在线音乐播放器-----酷狗音乐api接口抓取

    首先身为一个在线音乐播放器,需要前端和数据库的搭配使用. 在数据库方面,我们没有办法制作,首先是版权问题,再加上数据量.所以我们需要借用其他网络播放器的数据库. 但是这些在线播放器,如百度,酷狗,酷我 ...

  3. 百度音乐API抓取

    百度音乐API抓取 前段时间做了一个本地音乐的播放器 github地址,想实现在线播放的功能,于是到处寻找API,很遗憾,不是歌曲不全就是质量不高.在网上发现这么一个APIMRASONG博客,有“获取 ...

  4. 【QQ音乐Api】移花接木 打造自己的音乐电台

    最近突发奇想想做个在线音乐小网页.需求很简单,如下 搜索歌曲 或 歌手 在线播放音乐 借用qq 或者 百度的 音乐接口 需求明确那就直接动手了 我首先尝试的百度音乐,但是不能在线播放(提示forbid ...

  5. iOS在线音乐播放SZKAVPlayer(基于AVPlayer的封装)

    由于最近闲着没事,想找有关在线音乐播放的demo学习一下,在gitHub跟code4APP上面查找了很多帖子,结果很多在线音乐都是基于AudioStream实现的,我感觉用起来不太方便.后来突然发现, ...

  6. 如何使用 python 爬取酷我在线音乐

    前言 写这篇博客的初衷是加深自己对网络请求发送和响应的理解,仅供学习使用,请勿用于非法用途!文明爬虫,从我做起.下面进入正题. 获取歌曲信息列表 在酷我的搜索框中输入关键词 aiko,回车之后可以看到 ...

  7. Andriod小项目——在线音乐播放器

    转载自: http://blog.csdn.net/sunkes/article/details/51189189 Andriod小项目——在线音乐播放器 Android在线音乐播放器 从大一开始就已 ...

  8. 在线音乐网站【04】Part two 功能实现

       上一篇博客里面已近总结了三个功能的具体实现,今天把剩余功能的具体实现补充总结,如果你想对整个小项目有清楚的了解,建议去看下前几篇博客. 1.在线音乐网站(1)需求和功能结构 2.在线音乐网站(2 ...

  9. 在线音乐网站【03】Part one 功能实现

    今天打算把网站功能的具体实现给总结一下,如果你想了解整个小项目,建议你先看看前面2篇博客. 1.在线音乐网站(1)需求和功能结构 2.在线音乐网站(2)数据库和开发环境 7.网站主要模块实现 a.在线 ...

随机推荐

  1. C++中四种强制类型转换方式

    类型转换有c风格的,当然还有c++风格的.c风格的转换的格式很简单(TYPE)EXPRESSION,但是c风格的类型转换有不少的缺点,有的时候用c风格的转换是不合适的,因为它可以在任意类型之间转换,比 ...

  2. ACM-ICPC 2018 南京赛区网络预赛 L. Magical Girl Haze

    262144K   There are NN cities in the country, and MM directional roads from uu to v(1\le u, v\le n)v ...

  3. PAT Basic 1018

    1018 锤子剪刀布 大家应该都会玩“锤子剪刀布”的游戏:两人同时给出手势,胜负规则如图所示: 现给出两人的交锋记录,请统计双方的胜.平.负次数,并且给出双方分别出什么手势的胜算最大. 输入格式: 输 ...

  4. RHEL6.5上升级OpenSSH7.4p1

    由于升级OpenSSH涉及到安全性问题,为保险起见,在升级前最好安装telnet服务作为备用方案,然后在升级成功后再停止telnet即可. 一.OpenSSH升级相关源码包下载地址 zlib  htt ...

  5. Python第三方库之openpyxl(12)

    Python第三方库之openpyxl(12) 地面天气图 在工作表上的列或行中安排的数据可以在一个表中绘制.当您想要在两组数据之间找到最佳组合时,一个表面图表是有用的.正如在地形图中一样,颜色和图案 ...

  6. 【Kubernetes】声明式API与Kubernetes编程范式

    什么是声明式API呢? 答案是,kubectl apply命令. 举个栗子 在本地编写一个Deployment的YAML文件: apiVersion: apps/v1 kind: Deployment ...

  7. SoapUI offen used scripts

    [ Groovy Step ] //get properties from testCase, testSuite and project in groovy step def testCasePro ...

  8. web文件上传大小限制

    最近在项目中遇到上传文件,对上传文件的大小需要进行限制,这里学习和整理了一下一些常规的文件大小限制的方法. 一般分为两种方式,一种是服务器端判断文件大小进行限制,这种方法的存在明显的缺陷,当用户过多后 ...

  9. Mysql 主外键与索引之间的区别和联系

    系数据库依赖于主键,它是数据库物理模式的基石.主键在物理层面上只有两个用途: 惟一地标识一行. 作为一个可以被外键有效引用的对象. 索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部 ...

  10. BZOJ1925 [Sdoi2010]地精部落 【dp】

    题目 传说很久以前,大地上居住着一种神秘的生物:地精. 地精喜欢住在连绵不绝的山脉中.具体地说,一座长度为 N 的山脉 H可分 为从左到右的 N 段,每段有一个独一无二的高度 Hi,其中Hi是1到N ...