即将毕业的我,选择了做一款基于Android的音视屏的的软件来作为毕业答卷。在后面我会持续更新,记录下这四年的最后时光^__^。
(一)音频部分
1.这里采用了Android中的MedioPlayer这个工具来对音乐进行播放。
2.运用Server和Receiver这两大组件来完成音乐的后台持续播放和UI的更新。
3.使用MediaMetadataRetriever该工具类来完成对本地音频文件的解析,获取音频的详细信息。
4.使用通知,将后台播放的音乐信息展示到通知栏。
5.音频的剪辑。
6.其他。
(二)视屏部分
1.SurfaceView结合MediaPlayer完成视屏的播放。
2.弹幕.
3.其他。
以上的功能还只是最初的计划,现在还在做,我也不大知道要加哪些功能,做到后面再不断添加。
项目结构
adapter:适配器相关
bean:数据承载类相关
config:全局常量相关
db:数据库相关
net:网络相关
receiver:广播相关
relect:反射工具相关
server:服务相关
ui:界面相关
util:工具类相关
widget:自定义View相关
网络相关
服务相关
这里考虑到音乐播放是一个长时间的,不依赖于Activity的一种事件,所以我选择的是startServer()这种方式,该服务是独立的,不会与Activity关联的,地方Activity销毁或者pause的时候,服务任然在执行着。
广播相关
这里的广播,分为两种,一种是静态的,一种是动态的。有这样两种情景,一种时控制音乐的播放,暂停,下一曲,上一曲的这种交互需要设置成静态,当有音乐播放的时候就要有该广播,所以要为静态;一种是播放器的播放信息,例如当前歌曲的基本信息,这样的我觉得可以用动态的,当Activity创建的时候注册广播,当Activity销毁的时候就注销广播,这样就节省资源。
音频解析相关
- TAG_V1部分是MP3文件的最后128byte的内容.期中包括的信息有:
标签头”TAG” 3字节
标题 30字节
作者 30字节
专辑 30字节
出品年份 4字节
备注信息 28字节
保留 1字节
音轨 1字节
类型 1字节
方案一:
private static void randomReadMusicBasicInfo(File file, List<Musicer> musicerList) {
try {
if (file == null || musicerList == null) return;
byte[] bytes = new byte[128];
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
int p = randomAccessFile.read() << 8 + randomAccessFile.read();
String code = "UTF-8";
byte[] b = new byte[3];
randomAccessFile.read(b);
if (b[0] == -17 && b[1] == -69 && b[2] == -65)
code = "UTF-8";
else
code = "GBK";
randomAccessFile.seek(file.length() - 128);
randomAccessFile.read(bytes);
randomAccessFile.close();
String tga = new String(bytes, 0, 3, code);
Musicer musicer = new Musicer();
musicer.setPath(file.getAbsolutePath());
musicer.setMusictitle(file.getName());
if ("TAG".equalsIgnoreCase(tga)) {
String musictitle = new String(bytes, 3, 30, code);
String author = new String(bytes, 33, 30, code);
String album = new String(bytes, 63, 30, code);
String year = new String(bytes, 93, 4, code);
String memo = new String(bytes, 97, 28, code);
String retain = new String(bytes, 125, 1, code);
String track = new String(bytes, 126, 1, code);
String type = new String(bytes, 127, 1, code);
musicer.setMusictitle(musictitle);
musicer.setAuthor(author);
musicer.setAlbum(album);
musicer.setYear(year);
musicer.setMemo(memo);
musicer.setRetain(retain);
musicer.setTrack(track);
musicer.setType(type);
}
musicerList.add(musicer);
} catch (Exception e) {
e.printStackTrace();
}
}
方案二:
private static void getMusicInfo(File file, List<Musicer> musicerList){
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
try
{
Musicer musicer = new Musicer();
musicer.setPath(file.getAbsolutePath());
musicer.setMusictitle(file.getName());
mmr.setDataSource(file.getAbsolutePath());
String title = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
String album = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
String artist = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
String duration = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); // 播放时长单位为毫秒
byte[] pic = mmr.getEmbeddedPicture(); // 图片,可以通过BitmapFactory.decodeByteArray转换为bitmap图
musicer.setMusictitle(title);
musicer.setAuthor(artist);
musicer.setAlbum(album);
musicerList.add(musicer);
}
catch (Exception e)
{
e.printStackTrace();
}
}
这里可能是由于编码的问题和TAG的不统一档案已还存在问题,方案二好一些,这是Android内部实现的一个工具,源码里面是native方法,无法查看,这里也就不再深究。
沉浸状态栏
这里是在Android4.4以上才有效果,将状态栏设置为透明的,在将我们设置的contentView–root设置一个top方向的padding,这样就可以定制我们的状态栏背景了,但是不能兼容到Android4.4以下的版本,状态栏的高度我们可以通过反射拿到这个字段。
protected void initStatus(){
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
root.setPadding(0,getStatusH(),0,0);
}
public int getStatusH(){
int statusBarHeight = -1;
try {
Class<?> clazz = Class.forName("com.android.internal.R$dimen");
Object object = clazz.newInstance();
int height = Integer.parseInt(clazz.getField("status_bar_height")
.get(object).toString());
statusBarHeight = getResources().getDimensionPixelSize(height);
return statusBarHeight;
} catch (Exception e) {
e.printStackTrace();
}
return statusBarHeight;
}
返回键相关
大部分音乐播放器软件的back键是被修改了的,当点击返回键我们是不会退出应用的,而是返回到桌面。这样我们的后台音乐才能继续播放。当我们修改back事件后,我们Activity状态是被保存了,但是所有的Activity还是会启动一次,那我就让启动页设置为透明,选择性的跳转到start或者直接跳转到Index。
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
Intent home = new Intent();
home.setAction(Intent.ACTION_MAIN);
home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
home.addCategory(Intent.CATEGORY_HOME);
startActivity(home);
BaseApplication.getInstance().setNeedStartActivity(false);
return false;
}
return super.onKeyUp(keyCode, event);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().getDecorView().setBackgroundColor(Color.TRANSPARENT);
setContentView(R.layout.activity_start_controal);
if (BaseApplication.getInstance().isNeedStartActivity()){
startActivity(new Intent(this,StartActivity.class));
}else {
startActivity(new Intent(this,IndexPlayActivity.class));
}
finish();
}
通过上述思路,在我们按下back键的时候,应用会回到桌面,再次点击应用图标的时候,我们就会看到,我们的应用恢复到了按下back之前的状态。
好了,目前就这么多,项目地址:https://github.com/yzzAndroid/AudioVideo