- 浏览: 5730 次
- 性别:
- 来自: 惠州
文章分类
最新评论
-
javaandlemon1:
oezx 写道好像是这个引起的。#b { widt ...
暴力杀死IE,看看IE多磨脆弱 -
oezx:
好像是这个引起的。#b { width:100%; ...
暴力杀死IE,看看IE多磨脆弱
最近学习android开发做了一个播放器练手,同样是新手可以看看交流交流,呵呵,有什么更好的实现方法忘能指教一下
图效果在附件
主activity audioList.java
package com.xianyifa.audioplayer; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.Color; import android.media.MediaPlayer; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.util.DisplayMetrics; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MenuItem.OnMenuItemClickListener; import android.view.View; import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.TextView; import android.widget.Toast; import com.xianyifa.audioplayer.impl.MyPlayer; import com.xianyifa.audioplayer.util.Toolbox; public class AudioList extends Activity { private final String TAG = "AudioList"; private String filepath;//音乐文件绝对路径 private String longClickFilePath;//长按的音乐文件绝对路径 private int position = 0;//当前播放位置 private MyPlayer myPlayerService;//播放服务对象 private MyServiceConnection conn; private Intent service;// 音乐播放服务意图 private String audioFile;// 音乐文件所在文件夹 private ListView listView; private List<HashMap<String, Object>> data;// listView的数据 private long audioLength;// 播放音乐长度 private SimpleAdapter adapter;// ListView适配器 private int listId = -1;// 当前播放的音乐在listView的索引 private int onListId = -1;// 上一首播放的音乐在listView的索引 private MediaPlayer mediaPlayer;// 服务的播放器 private Handler handler;// 用于主线程和子线程的通讯 private ControlPlayTime controlPlayTime;// 音乐时间更新控制线程 private boolean controlPlayStop = false;//控制音乐时间更新控制线程结束 private boolean isStop = false;// 标识播放器是否暂停 private boolean isPause = false;// 标识activity是否是在暂停恢复 private int widthPixels;//设备屏幕宽度像素 private final int ADDAUDIOPLAYER_ID = Menu.FIRST; private final int DELAUDIOPLAYER_ID = Menu.FIRST + 1; private final int EXITAUDIOPLAYER_ID = Menu.FIRST + 2; private final int DELETE_DIALOG = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.audiolist); service = new Intent(this, PlayerService.class);// 通过意图取得播放服务 // 驱动服务,1、,2、激活后回返回一个通道,Activity和service通讯是通过通道通讯的,服务通道是一个接口要实现 // 3、常量1自动创建 conn = new MyServiceConnection(); this.startService(service);// 先使用创建服务在绑定 this.bindService(service, conn, BIND_AUTO_CREATE); showListView(); //获取屏幕的宽带 DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); widthPixels = dm.widthPixels; Log.i(TAG, "onCreate"); } /* * 把数据绑定listview,并在界面显示 */ private void showListView(){ Log.i(TAG, "showListView"); // 读取文件夹的音乐列表 // 判断是否存在SD卡 File file; if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { audioFile = Environment.getExternalStorageDirectory() + "/myaudio"; file = new File(audioFile); // 判断目录是否存在 if (!file.exists()) { file.mkdirs(); } } else { audioFile = "/myaudio"; file = new File(audioFile); // 判断目录是否存在 if (!file.exists()) { file.mkdirs(); } } data = new ArrayList<HashMap<String, Object>>(); data = Toolbox.showCatalog(file); // 取得listview listView = (ListView) findViewById(R.id.audiolist); adapter = new MyAdapter(AudioList.this, data, R.layout.audiolistitem, new String[] { "filename", "filepath", "playTime", "audioTime" }, new int[] { R.id.audioname, R.id.audiopath, R.id.audioplaytime, R.id.audiotime }); listView.setAdapter(adapter); // 创建播放控制线程 controlPlayTime = new ControlPlayTime();// 取得播放时间控制线程 // 创建线程通讯监听 handler = new Handler() { @Override public void handleMessage(Message msg) { String message = (String) msg.obj; //如果message是数字,就是发过来的歌曲长度,不是则是转换后的播放时间点 if(message.matches("[0-9]+")){ //这是服务在播放,用户重新回到activity界面时更新UI显示当前播放歌曲信息 HashMap<String, Object> item = (HashMap<String, Object>) listView.getItemAtPosition(listId); item.put("audioTime", "/"+Toolbox.lengthTime(Long.parseLong(message))); }else{ if (onListId == listId) { HashMap<String, Object> item = (HashMap<String, Object>) listView.getItemAtPosition(listId); item.put("playTime", message); } else { // 当是服务换歌是在这里更新UI显示 HashMap<String, Object> playItem = (HashMap<String, Object>) listView.getItemAtPosition(listId); HashMap<String, Object> item = (HashMap<String, Object>) listView.getItemAtPosition(onListId); audioLength = myPlayerService.getPlayLength(); String time = Toolbox.lengthTime(audioLength); playItem.put("audioTime", "/" + time); playItem.put("playTime", "0:00"); item.put("audioTime", ""); item.put("playTime", ""); onListId = listId;//不要忘了这部要不然时间不会跳动 } } adapter.notifyDataSetChanged(); super.handleMessage(msg); } }; // 为listView注册单击事件 listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ListView v = (ListView) parent; HashMap<String, Object> item = (HashMap<String, Object>) v.getItemAtPosition(position); // 上个播放的文件在ListView的位置 HashMap<String, Object> Befitem = null; if (listId > -1) { Befitem = (HashMap<String, Object>) v.getItemAtPosition(listId); } listId = position;// 保存listView索引 onListId = listId; filepath = item.get("filepath").toString();// 取得音乐路径 String playerFileName = myPlayerService.getFilePath(); if (mediaPlayer.isPlaying()) {// 如果正在播放 if (playerFileName.equals(filepath)) {// 而且请求的路径和现在播放的路径一样 myPlayerService.pause();// 暂停它 isStop = true; } else { try { myPlayerService.play(filepath, AudioList.this.position,listId); audioLength = mediaPlayer.getDuration(); String time = Toolbox.lengthTime(audioLength); item.put("audioTime", "/" + time); item.put("playTime", "0:00"); if (!Befitem.isEmpty() || Befitem != null) {// 吧上个播放的文件总时间删除 Befitem.put("audioTime", ""); Befitem.put("playTime", ""); } adapter.notifyDataSetChanged();// 让ListView更新 } catch (IOException e) { Log.i(TAG, e.toString()); } } } else {// 如果不是在播放 if (isStop && playerFileName.equals(filepath)) {// 判断是不是停止状态并且请求播放的是同一个文件 myPlayerService.pause(); } else {// 不是暂停状态的调用播放,或者是暂停但是请求的不是同一个音乐文件 try { myPlayerService.play(filepath, AudioList.this.position,listId); audioLength = mediaPlayer.getDuration(); String time = Toolbox.lengthTime(audioLength); // 判断线程是否活动状态 if (!controlPlayTime.isAlive()) { controlPlayTime.start();// 第一次执行播放开始线程 } item.put("audioTime", "/" + time); item.put("playTime", "0:00"); if (Befitem != null) {// 把上个播放的文件总时间删除,只有第一次播放和暂停换歌才会在这里掉用播放,只有暂停换歌才清空时间 Befitem.put("audioTime", ""); Befitem.put("playTime", ""); } adapter.notifyDataSetChanged();// 让ListView更新 } catch (IOException e) { Log.i(TAG, e.toString()); } } } } }); //为listview创建上文菜单 listView.setOnCreateContextMenuListener(new OnCreateContextMenuListener() { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { //设置图标 menu.setHeaderIcon(R.drawable.content_menu_ico); //设置标题 menu.setHeaderTitle(R.string.choice_action); //设置菜单 //播放 menu.add(R.string.player).setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { // TODO Auto-generated method stub return true; } }); //删除 menu.add(R.string.delete).setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { showDialog(DELETE_DIALOG);//显示提示框 return true; } }); } }); //为ListView创建一个item长按监听 listView.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { ListView v = (ListView)parent; HashMap<String, Object> item = (HashMap<String, Object>)v.getItemAtPosition(position); longClickFilePath = item.get("filepath").toString(); listView.showContextMenu(); return true; } }); } private class MyAdapter extends SimpleAdapter{ public MyAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to) { super(context, data, resource, from, to); // TODO Auto-generated constructor stub } /* * 每次加载listView都会调用 * (non-Javadoc) * @see android.widget.SimpleAdapter#getView(int, android.view.View, android.view.ViewGroup) */ @Override public View getView(int position, View convertView, ViewGroup parent) { convertView = LayoutInflater.from(AudioList.this.getApplicationContext()).inflate(R.layout.audiolistitem, null); TextView fileNameText = (TextView)convertView.findViewById(R.id.audioname); TextView filePathText = (TextView)convertView.findViewById(R.id.audiopath); TextView playTimeText = (TextView)convertView.findViewById(R.id.audioplaytime); TextView fileTimeText = (TextView)convertView.findViewById(R.id.audiotime); TextView progressBarText = (TextView)convertView.findViewById(R.id.progress_bar); //比重新给值将显示空白?位置原因:getView就是把每天数据绑定到界面的过程,所以在这里要赋值 fileNameText.setText(((HashMap<String, Object>)listView.getItemAtPosition(position)).get("filename").toString()); filePathText.setText(((HashMap<String, Object>)listView.getItemAtPosition(position)).get("filepath").toString()); playTimeText.setText(((HashMap<String, Object>)listView.getItemAtPosition(position)).get("playTime").toString()); fileTimeText.setText(((HashMap<String, Object>)listView.getItemAtPosition(position)).get("audioTime").toString()); if(listId == position){ //他要求传int 但不能传颜色的十进制代码 fileNameText.setTextColor(Color.parseColor("#3197FF")); fileNameText.setTextColor(Color.parseColor("#3197FF")); playTimeText.setTextColor(Color.parseColor("#3197FF")); fileTimeText.setTextColor(Color.parseColor("#3197FF")); //修改进度条长度 LayoutParams laParaContent = (LayoutParams)progressBarText .getLayoutParams(); laParaContent.width = getprogressBarSize(); progressBarText.setLayoutParams(laParaContent); // progressBarText.setWidth(0);//用这个更改不了 }else{ fileNameText.setTextColor(Color.parseColor("#000000")); } return convertView; } } /* * 计算进度条的尺寸 */ private int getprogressBarSize(){ double proportion = (double)mediaPlayer.getCurrentPosition()/(double)myPlayerService.getPlayLength(); int px = (int)(widthPixels * proportion); return px; } /* * 因为系统内存不足被摧毁 (non-Javadoc) * * @see android.app.Activity#onRestoreInstanceState(android.os.Bundle) */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { this.position = savedInstanceState.getInt("position"); this.filepath = savedInstanceState.getString("filepath"); Log.i(TAG, "onRestoreInstanceState"); super.onRestoreInstanceState(savedInstanceState); } /* * 因为系统内存不足被摧毁 (non-Javadoc) * * @see android.app.Activity#onSaveInstanceState(android.os.Bundle) */ @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt("position", myPlayerService.getPosition()); outState.putString("filepath", myPlayerService.getFilePath()); Log.i(TAG, "onSaveInstanceState"); super.onSaveInstanceState(outState); } /* * 暂停了Activity (non-Javadoc) * * @see android.app.Activity#onPause() */ @Override protected void onPause() { if (myPlayerService != null) { myPlayerService.showNotification(); isPause = true; } Log.i(TAG, "onPause"); super.onPause(); } /* * 重新唤起,或刚开启都会调用 (non-Javadoc) * * @see android.app.Activity#onResume() */ @Override protected void onResume() { if (isPause && (myPlayerService != null)) { myPlayerService.hideNotification(); isPause = false; } Log.i(TAG, "onResume"); super.onResume(); } /* * 添加菜单 (non-Javadoc) * * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) */ @Override public boolean onCreateOptionsMenu(Menu menu) { // TODO Auto-generated method stub super.onCreateOptionsMenu(menu); // 退出程序 menu.add(0, ADDAUDIOPLAYER_ID, 0, "添加歌曲").setShortcut('2', 'b'); menu.add(0, DELAUDIOPLAYER_ID, 0, "删除歌曲").setShortcut('2', 'b'); // .setIcon(R.drawable.exit); // 退出程序 menu.add(0, EXITAUDIOPLAYER_ID, 0, "退出").setShortcut('4', 'd') .setIcon(R.drawable.exit); return true; } /* * 处理菜单动作 (non-Javadoc) * * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) */ @Override public boolean onOptionsItemSelected(MenuItem item) { // TODO Auto-generated method stub switch (item.getItemId()) { case EXITAUDIOPLAYER_ID: // 退出播放器 this.finish();//这里会执行解除绑定 controlPlayStop = true;//控制播放线程也结束 //等待播放控制线程结束才停止播放服务 while(controlPlayTime.isAlive()){ try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block Log.e(TAG, e.toString()); } Log.i(TAG, "wait controlPlayTime stop"); } this.stopService(service); return true; } return super.onOptionsItemSelected(item); } /* * 实现通道 */ private final class MyServiceConnection implements ServiceConnection { /* * 链接服务调用方法 service 为binder 通讯的桥梁 (non-Javadoc) * * @see * android.content.ServiceConnection#onServiceConnected(android.content * .ComponentName, android.os.IBinder) */ @Override public void onServiceConnected(ComponentName name, IBinder service) { // Binder binder = (Binder)service; myPlayerService = (MyPlayer) service; myPlayerService.setListViewData(data); listId = myPlayerService.getListId(); onListId = listId;//当前播放歌曲的listView数据索引 mediaPlayer = myPlayerService.getMediaPlayer();// 取得服务中的播放器 // 判断线程是否活动状态,并且音乐服务在播放 在这里就启动更新时间线程 if (myPlayerService.getIsPlayInit()) { audioLength = myPlayerService.getPlayLength();//当前播放歌曲的长度 controlPlayTime.start();// 第一次执行播放开始线程 } Log.i(TAG, "onServiceConnected"); } /* * 断开服务调用方法 (non-Javadoc) * * @see * android.content.ServiceConnection#onServiceDisconnected(android.content * .ComponentName) */ @Override public void onServiceDisconnected(ComponentName name) { myPlayerService = null; Log.i(TAG, "onServiceDisconnected"); } } /* * 播放时间更新控制线程,只有播放器存在才会启动 */ public class ControlPlayTime extends Thread { @Override public void run() { //线程刚启动就发给handler让他更新UI播放的音乐总长度 Message message1 = Message.obtain(); message1.obj = audioLength+"";//audioLength是long行要转换字符串传递 handler.sendMessage(message1); // 判断歌曲是否还在播放 while (!controlPlayStop) { long milliSecond = mediaPlayer.getCurrentPosition(); String time = Toolbox.formatTime(milliSecond); Message message = Message.obtain();// 用于和住线程通讯的消息容器,每次通讯都要创建 message.obj = time; handler.sendMessage(message); if((audioLength - milliSecond <= 1100)){ Log.i(TAG, "waiting next song"); while(audioLength == myPlayerService.getPlayLength()){ milliSecond = mediaPlayer.getCurrentPosition(); time = Toolbox.formatTime(milliSecond); Message message2 = Message.obtain();// 用于和住线程通讯的消息容器,每次通讯都要创建 message2.obj = time; handler.sendMessage(message2); Log.i(TAG, "ControlPlayTime waiting next song"); try { Thread.sleep(50); } catch (InterruptedException e) { Log.i(TAG, e.toString()); } } Log.i(TAG, "ControlPlayTime star updata next song playtime"); listId = myPlayerService.getListId(); //立即向handler发一个消息,这个消息内容没什么意义,只是使handler为下一首歌曲界面显示初始化 Message message3 = Message.obtain();// 用于和住线程通讯的消息容器,每次通讯都要创建 message3.obj = "0:00"; handler.sendMessage(message3); } try { Thread.sleep(1000); } catch (InterruptedException e) { Log.e(TAG, e.toString()); } } } } /* * 创建弹出确认窗口 * (non-Javadoc) * @see android.app.Activity#onCreateDialog(int, android.os.Bundle) */ @Override protected Dialog onCreateDialog(int id) { switch (id) { case DELETE_DIALOG: return new AlertDialog.Builder(AudioList.this) .setTitle(R.string.prompt).setMessage(getString(R.string.verify_del) +"\""+ longClickFilePath.substring(longClickFilePath.lastIndexOf("/")+1, longClickFilePath.length())+"\"?") .setPositiveButton(R.string.verify, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //确定执行操作 removeDialog(DELETE_DIALOG);//吧创建的弹出删除,不删除下次创建还是同一个对象,导致消息内容不变 } }).setNegativeButton(R.string.cancel, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { removeDialog(DELETE_DIALOG); } }).create(); default: return null; } } @Override protected void onDestroy() { controlPlayStop = true;//控制播放线程也结束 //等待播放控制线程结束才停止播放服务 while(controlPlayTime.isAlive()){ try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block Log.e(TAG, e.toString()); } Log.i(TAG, "wait controlPlayTmie stop"); } unbindService(conn); Log.i(TAG, "Activity onDestroy"); super.onDestroy(); } }
控制播放的服务PlayerService.java
package com.xianyifa.audioplayer; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.media.MediaPlayer; import android.os.Binder; import android.os.IBinder; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; import com.xianyifa.audioplayer.impl.MyPlayer; public class PlayerService extends Service { private final String TAG = "PlayerService"; private MediaPlayer mediaPlayer;//实例化一个播放器; private String filepath = null;//文件绝对路径 private int position;//播放的进度 private long playLength;//正在播放音乐的长度 private boolean isStop = false; private boolean controlPlayStop = false;//播放否控制 private boolean isPlayInit = false;//播放器是否初始化 private List<HashMap<String, Object>> listViewData;// listView的数据 private ControlPlay controlPlay;//播放控制线程 private int listId = -1; private boolean isShowNotification = false; private Binder binder = new MyBinder();//创建一个通讯,用于返回给调用,建立通讯桥梁,通讯都基于次桥梁 @Override public IBinder onBind(Intent arg0) { // 取得电话服务,实现电话进来的时候停止播放,挂断的继续 TelephonyManager telManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); // 注册监听,监听电话状态,并指定触发后执行的类,1、调用监听处理类方法,2、监听到的通讯状态(电话进入,接通电话,挂断电话) telManager.listen(new TelListenr(), PhoneStateListener.LISTEN_CALL_STATE); mediaPlayer = new MediaPlayer();//实例化一个播放器; controlPlay = new ControlPlay(); return binder; } public void setFilePath(String filepath){ this.filepath = filepath; } public void setPosition(int position){ this.position = position; } /* * 为Binder添加业务方法,只有在这里面才能通过Binder建立的通道进行调用 */ private final class MyBinder extends Binder implements MyPlayer{ public void setFilePath(String filepath){ PlayerService.this.setFilePath(filepath); } public void setListViewData(List<HashMap<String, Object>> listViewData){ PlayerService.this.listViewData = listViewData; } public void play(String filepath,int position,int id) throws IOException{ setFilePath(filepath);//在服务保存当前MP3路径 setPosition(position);//在服务保存当前MP3路径 listId = id; PlayerService.this.isStop = false; File file = new File(filepath); mediaPlayer.reset();//把之前的设置都重置一下 mediaPlayer.setDataSource(file.getAbsolutePath());//设置音乐文件路径 mediaPlayer.prepare();//缓存一定要调用,初始化 mediaPlayer.start(); mediaPlayer.seekTo(position); isPlayInit = true; playLength = mediaPlayer.getDuration(); if(!PlayerService.this.controlPlay.isAlive()){ controlPlay.start(); } } public boolean pause(){ if(mediaPlayer.isPlaying()){//如果是在播放 mediaPlayer.pause(); return true; }else{ //应为如果按了停止直接使用start 继续叫报错 if(!PlayerService.this.isStop){ mediaPlayer.start(); } return false; } } public void reset()throws IOException{ if(mediaPlayer.isPlaying()){ mediaPlayer.seekTo(0); }else if(PlayerService.this.filepath != null){//确保用户先点击过播放 play(PlayerService.this.filepath,0,listId); } } public void stop(){ if(mediaPlayer.isPlaying()){ mediaPlayer.stop(); PlayerService.this.isStop = true; } } public int getPosition(){ return mediaPlayer.getCurrentPosition(); } public String getFilePath(){ return PlayerService.this.filepath; } public MediaPlayer getMediaPlayer(){ return mediaPlayer; } public int getListId(){ return listId; } public boolean getIsPlayInit(){ return isPlayInit; } public long getPlayLength(){ return playLength; } /* * 显示通知栏图标,当界面不可见的时候调用 * (non-Javadoc) * @see com.xianyifa.audioplayer.impl.MyPlayer#showNotification() */ public void showNotification(){ // 创建一个NotificationManager的引用 NotificationManager notificationManager = (NotificationManager) PlayerService.this.getSystemService(android.content.Context.NOTIFICATION_SERVICE); // 定义Notification的各种属性 Notification notification =new Notification(R.drawable.icon, (filepath != null) ? filepath.substring(filepath.lastIndexOf("/")+1, filepath.length()) : "无音乐播放", System.currentTimeMillis()); notification.flags |= Notification.FLAG_ONGOING_EVENT; // 将此通知放到通知栏的"Ongoing"即"正在运行"组中 notification.flags |= Notification.FLAG_NO_CLEAR; // 表明在点击了通知栏中的"清除通知"后,此通知不清除,经常与FLAG_ONGOING_EVENT一起使用 notification.flags |= Notification.FLAG_SHOW_LIGHTS; notification.defaults = Notification.DEFAULT_LIGHTS; notification.ledARGB = Color.BLUE; notification.ledOnMS =5000; // 设置通知的事件消息 CharSequence contentTitle = "正在播放……"; // 通知栏标题 CharSequence contentText = (filepath != null) ? filepath.substring(filepath.lastIndexOf("/")+1, filepath.length()) :"无音乐播放"; // 通知栏内容 // CharSequence contentText = "无音乐播放"; // 通知栏内容 Intent notificationIntent = new Intent(PlayerService.this, AudioList.class); // 点击该通知后要跳转的Activity //添加这里可以解决当按home键停止activity在冲通知进入时出现多个activity对象 //也就是再按返回是跳到另一个还是这个界面的activity notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent contentItent = PendingIntent.getActivity(PlayerService.this, 0, notificationIntent, 0); notification.setLatestEventInfo(PlayerService.this, contentTitle, contentText, contentItent); // 把Notification传递给NotificationManager notificationManager.notify(0, notification); isShowNotification = true; } /* * 删除通知栏的图标 * (non-Javadoc) * @see com.xianyifa.audioplayer.impl.MyPlayer#hideNotification() */ public void hideNotification(){ // 启动后删除之前我们定义的通知 NotificationManager notificationManager = (NotificationManager) PlayerService.this .getSystemService(NOTIFICATION_SERVICE); notificationManager.cancel(0); isShowNotification = false; } } /* * 实现电话状态类 */ private class TelListenr extends PhoneStateListener { @Override public void onCallStateChanged(int state, String incomingNumber) { try { switch (state) { case TelephonyManager.CALL_STATE_IDLE:// 挂断 PlayerService.this.mediaPlayer.start(); break; case TelephonyManager.CALL_STATE_OFFHOOK:// 接通电话 break; case TelephonyManager.CALL_STATE_RINGING:// 电话进入 if(PlayerService.this.mediaPlayer.isPlaying()){//如果是在播放 PlayerService.this.mediaPlayer.pause();//暂停 } break; default: break; } } catch (Exception e) { // TODO Auto-generated catch block Log.i(TAG, e.toString()); } super.onCallStateChanged(state, incomingNumber); } } /* * 播放控制线程 */ public class ControlPlay extends Thread { @Override public void run() { while (!controlPlayStop) { try { Thread.sleep(800); } catch (InterruptedException e) { Log.e(TAG, e.toString()); } long milliSecond = mediaPlayer.getCurrentPosition(); if((playLength - milliSecond) <= 1100){ // String filepath = filename; String audioName = filepath.substring(filepath.lastIndexOf("/")+1, filepath.length()); //第一次换歌没有初始化,先找到当前在listView的索引以后往上加就可以知道列表末尾 if(listId == -1){ listId = getListId(audioName, filepath); } if(listId < (listViewData.size() - 1)){ listId += 1; }else{ listId = 0; } String playPath = getFilePath(listId); Log.i(TAG, listId+"------"+playPath+"----"+listViewData.size()); MyPlayer myPlayer = (MyBinder)binder; try { Log.i(TAG, "service waiting 3 Second play next song"); Thread.sleep(3000); myPlayer.play(playPath, 0,listId); } catch (Exception e) { Log.e(TAG, e.toString()); } //判断通知是不是显示,做出对通知信息的更改 if(isShowNotification){ myPlayer.hideNotification(); myPlayer.showNotification(); } } } } } /* * data list绑定的数据 * filepath 正在播放歌曲的路径 * filename 正在播放歌曲名称 * return i 返回正在播放的歌曲在listView绑定数据的索引 */ private int getListId(String filename,String filepath){ int i = 0; for(HashMap<String,Object> audio : listViewData){ if(audio.get("filepath").equals(filepath)){ break; } i++; } return i; } /* * index 歌曲在listView绑定数据的索引 * return 返回歌曲的绝对路径 */ private String getFilePath(int index){ HashMap<String,Object> audio = listViewData.get(index); return audio.get("filepath").toString(); } @Override public void onDestroy() { controlPlayStop = true;//控制播放线程也结束 //等待播放控制线程结束才停止播放服务 while(controlPlay.isAlive()){ try { Thread.sleep(1000); } catch (InterruptedException e) { Log.e(TAG, e.toString()); } Log.i(TAG, "wait controlPlay stop"); } //播放控制线程停止后在停止播放器 if(mediaPlayer != null){ MyPlayer myPlayer = (MyBinder)binder; myPlayer.hideNotification();//服务结束的时候一定要清楚通知栏 mediaPlayer.stop();//一定要在这里停止,要不然服务停止了播放器还是会继续播放 mediaPlayer.release();//释放资源 } super.onDestroy(); } }
实现服务和activity通讯的binder继承类的接口
package com.xianyifa.audioplayer.impl; import java.io.IOException; import java.util.HashMap; import java.util.List; import android.media.MediaPlayer; import android.os.Handler; public interface MyPlayer { public void play(String filename,int position,int id) throws IOException; public boolean pause(); public void reset() throws IOException; public void stop(); public void setFilePath(String filepath); public void showNotification(); public void hideNotification(); public int getPosition(); public String getFilePath(); public MediaPlayer getMediaPlayer(); public void setListViewData(List<HashMap<String, Object>> listViewData); public int getListId(); public boolean getIsPlayInit(); public long getPlayLength(); }
主界面XMLaudiolist.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="@android:color/white" > <ListView android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/audiolist" android:cacheColorHint="#00000000" > </ListView> </LinearLayout>
item.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:id="@+id/listviewitem" > <LinearLayout android:layout_width="fill_parent" android:layout_height="40dip" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/audioname" android:layout_marginLeft="20dip" android:layout_marginTop="10dip" android:textColor="@android:color/black" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" android:layout_marginTop="10dip" android:layout_marginRight="20dip" android:gravity="right"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/audioplaytime" android:layout_gravity="right" android:textColor="@android:color/black" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/audiotime" android:textColor="@android:color/black" /> </LinearLayout> <TextView android:layout_width="0dip" android:layout_height="0dip" android:id="@+id/audiopath" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="1dip" android:orientation="horizontal" > <TextView android:layout_width="0px" android:layout_height="fill_parent" android:id="@+id/progress_bar" android:background="@android:color/black" /> </LinearLayout> </LinearLayout> 工具类 Toolbox .java package com.xianyifa.audioplayer.util; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class Toolbox { // 调用文件目录查询方法 //方法一;以绝对路径输出给定的目录下的所有文件路径 public static List<HashMap<String, Object>> showCatalog(File file) { //System.out.println(file.getName()); List<HashMap<String, Object>> fileList = new ArrayList<HashMap<String, Object>>(); File[] files = file.listFiles(); if (files != null) { for (File f : files) { if (f.isDirectory()) {//判断是否是目录 showCatalog(f); } else { // System.out.println(f.getName());//输出文件或文件夹名称 if(f.getName().substring(f.getName().lastIndexOf("."), f.getName().length()).equals(".mp3")){ HashMap<String, Object> item = new HashMap<String, Object>(); item.put("filepath",f.getAbsolutePath());//输出绝对路径 item.put("filename",f.getName());//输出绝对路径 item.put("playTime","");//输出绝对路径 item.put("audioTime","");//输出绝对路径 fileList.add(item); } } } } return fileList; } /* * 格式化当前播放时间点时间 */ public static String formatTime(long milliSecond){ int minute = (int)(milliSecond/1000)/60; int second = (int)(milliSecond/1000)%60; String sec = second+""; if(second<10){ sec = "0"+second; } String time = minute+":"+sec; return time; } /* * 求总时长 */ public static String lengthTime(long milliSecond){ int minute = (int)(milliSecond/1000)/60; int second = (int)(milliSecond/1000)%60; if(milliSecond%1000 > 500){ second += 1; if(second == 60){ minute += 1; second = 0; } } String sec = second+""; if(second<10){ sec = "0"+second; } String time = minute+":"+sec; return time; } }
新上传的源码修改了一些小地方,服务播放变更改用广播通知activity;感觉用这个比较好,有利以后实现歌词同步。有空在研究实现歌词同步 |
- AudioPlayer.rar (182.6 KB)
- 下载次数: 59
- AudioPlayer11.16.zip (203.7 KB)
- 下载次数: 64
相关推荐
Android手写识别SDK是Google ML Kit的一个重要子组件,它为开发者提供了强大的工具,以便在Android应用程序中实现手写文字的识别和解析。ML Kit是Google提供的机器学习服务框架,旨在简化移动应用开发中的复杂AI功能...
在手写签字应用中,可能有一个`SignatureActivity`负责展示签字界面,用户在此界面上进行手写操作。`layout`目录存放了应用的XML布局文件,这些文件定义了用户界面的布局和组件,比如一个自定义的`SignatureView`,...
回到正题:这是一个免费的手写识别引擎,可以二次开发,但也有前提的,具体自己看里面的文档吧,自己用很合适!这东西小巧、速度快,精度优秀,所以用了它以后开源的就可以下课啦。对了android 有两个版本,这个版本...
本资源"Android手机手写输入源码"提供了一个完整的工程,用于实现Android设备上的手写输入功能。下面将详细介绍这个项目中的关键知识点。 1. **Android工程结构**: - `.classpath`:这是Eclipse(或ADT插件)项目...
这个"drawDemo"项目可能包含了一个简单的示例,教你如何在Android应用中创建一个可交互的手写画板。下面将详细介绍实现这一功能的关键技术和步骤。 首先,你需要创建一个可以绘制的View。在Android中,自定义View是...
这份“Android高级应用源码-android的手写功能.rar”压缩包很可能包含了一个完整的Android项目,用于实现手写识别和绘图功能。为了深入理解并利用这些源码,我们需要探讨一些关键的技术点。 1. **手写识别技术**:...
在Android平台上,开发一款电子签名手写板是一个常见的需求,特别是在移动支付、文档签署和表单填写等场景中。这个项目的核心目标是提供一个用户友好的界面,让用户能够通过触屏进行手写输入,然后将签名转换为图像...
本文将深入探讨如何模拟OkHttp,手写一个简易版的网络访问框架。通过这样的实践,我们可以更好地理解网络请求的工作原理,以及OkHttp的核心特性。 首先,我们需要了解HTTP协议的基本概念。HTTP(超文本传输协议)是...
在这个项目中,Android源码包含了一个用于运行手写数字识别功能的APP。开发者可能使用了Android Studio作为集成开发环境(IDE),并利用Java或Kotlin语言编写了应用的逻辑代码。这部分代码可能会包含UI设计,如用于...
该压缩包文件“Android应用源码PDF手写签名商业源码.zip”主要包含了一个Android应用程序的源代码,用于实现PDF文档的手写签名功能。在移动设备上,这种功能对于签署电子文档尤其有用,例如合同、协议等。让我们深入...
本文将深入探讨如何构建一个具有类似银行电子签名功能的Android应用,包括核心技术和实现步骤。 首先,理解“电子签名”的概念至关重要。电子签名是一种通过数字方式表示的签名形式,它能够提供与传统手写签名同等...
DroidText0.5则是一个专为Android设计的文本渲染库,特别适用于在PDF上进行手写签名。这个库允许用户在屏幕上自由绘制,然后将这些绘制的笔迹转换为高质量的矢量图形,从而实现逼真的手写签名效果。DroidText0.5的...
这个"android 手写、涂鸦demo"是一个示例项目,展示了如何在Android应用程序中实现自定义的手绘功能。以下是对这个Demo涉及知识点的详细解释: 1. **画布(Canvas)**:在Android中,Canvas是用于绘制图形的基础类。...
在Android开发中,实现手写轨迹保存功能是一个比较复杂但极具实用价值的技术点。这个"android手写轨迹保存(防有道云笔记手写输入)"的项目,旨在提供一个能够记录用户手写输入并进行编辑的示例。下面将详细阐述这个...
如果需要实现更复杂的手写输入功能,如汉字识别,可以创建一个继承自`InputMethodService`的自定义输入法服务。通过重写`onCreateInputSession()`方法,创建一个`InputSession`,并在其中处理触控事件并进行识别。 ...
Android平台原生手写签字控件,功能强大的开源项目,支持空白画布手写,田字格逐字手写,撤销/恢复,橡皮擦,画笔设置等功能,支持笔锋效果。 Android 手写签批 画板 笔锋。 Android应用源码PDF手写签名商业源码。 ...
总的来说,“android手写输入”结合了先进的计算机视觉和人工智能技术,为用户提供了一个高效、自然的输入方式。无论是日常沟通还是学习汉字,Android手写输入都极大地丰富了用户的交互体验。随着技术的发展,我们...
"android 便签 手写签到demo"是一个专门为开发者准备的示例项目,它展示了如何在Android应用中实现手写签到功能,类似于小米手机内置的便签应用。这个demo对于想要学习和实现类似功能的开发者来说,具有很高的参考...
在手写签名中,我们需要创建一个`Path`对象来跟踪用户的笔触轨迹。 2. **MotionEvent**: `MotionEvent`事件用于处理触摸屏幕时的各种动作,包括按下、移动和抬起等。在手写签名中,我们需要监听这些事件来获取...
在Android平台上实现手写绘画功能,开发者通常会利用触摸屏事件和绘图API来创建一个用户可以自由绘制的界面。这个过程涉及到多个知识点,包括触摸事件处理、绘图API的使用以及权限管理。以下是关于"android 手写绘画...