`
javaandlemon1
  • 浏览: 5730 次
  • 性别: Icon_minigender_1
  • 来自: 惠州
社区版块
存档分类
最新评论

刚学android,练手写的一个播放器

阅读更多

最近学习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;感觉用这个比较好,有利以后实现歌词同步。有空在研究实现歌词同步
  • 大小: 118.7 KB
  • 大小: 111 KB
  • 大小: 156.1 KB
分享到:
评论

相关推荐

    android手写识别SDK

    Android手写识别SDK是Google ML Kit的一个重要子组件,它为开发者提供了强大的工具,以便在Android应用程序中实现手写文字的识别和解析。ML Kit是Google提供的机器学习服务框架,旨在简化移动应用开发中的复杂AI功能...

    Android手写签字源码

    在手写签字应用中,可能有一个`SignatureActivity`负责展示签字界面,用户在此界面上进行手写操作。`layout`目录存放了应用的XML布局文件,这些文件定义了用户界面的布局和组件,比如一个自定义的`SignatureView`,...

    Android手写识别

    回到正题:这是一个免费的手写识别引擎,可以二次开发,但也有前提的,具体自己看里面的文档吧,自己用很合适!这东西小巧、速度快,精度优秀,所以用了它以后开源的就可以下课啦。对了android 有两个版本,这个版本...

    Android手机手写输入源码

    本资源"Android手机手写输入源码"提供了一个完整的工程,用于实现Android设备上的手写输入功能。下面将详细介绍这个项目中的关键知识点。 1. **Android工程结构**: - `.classpath`:这是Eclipse(或ADT插件)项目...

    android实现简单的手写笔迹效果

    这个"drawDemo"项目可能包含了一个简单的示例,教你如何在Android应用中创建一个可交互的手写画板。下面将详细介绍实现这一功能的关键技术和步骤。 首先,你需要创建一个可以绘制的View。在Android中,自定义View是...

    Android高级应用源码-android的手写功能.rar

    这份“Android高级应用源码-android的手写功能.rar”压缩包很可能包含了一个完整的Android项目,用于实现手写识别和绘图功能。为了深入理解并利用这些源码,我们需要探讨一些关键的技术点。 1. **手写识别技术**:...

    Android电子签名手写板

    在Android平台上,开发一款电子签名手写板是一个常见的需求,特别是在移动支付、文档签署和表单填写等场景中。这个项目的核心目标是提供一个用户友好的界面,让用户能够通过触屏进行手写输入,然后将签名转换为图像...

    Android-模拟OkHttp手写简易版网络访问框架

    本文将深入探讨如何模拟OkHttp,手写一个简易版的网络访问框架。通过这样的实践,我们可以更好地理解网络请求的工作原理,以及OkHttp的核心特性。 首先,我们需要了解HTTP协议的基本概念。HTTP(超文本传输协议)是...

    Android手写数字识别源码.zip

    在这个项目中,Android源码包含了一个用于运行手写数字识别功能的APP。开发者可能使用了Android Studio作为集成开发环境(IDE),并利用Java或Kotlin语言编写了应用的逻辑代码。这部分代码可能会包含UI设计,如用于...

    Android应用源码PDF手写签名商业源码.zip

    该压缩包文件“Android应用源码PDF手写签名商业源码.zip”主要包含了一个Android应用程序的源代码,用于实现PDF文档的手写签名功能。在移动设备上,这种功能对于签署电子文档尤其有用,例如合同、协议等。让我们深入...

    Android 手写签名 电子签名

    本文将深入探讨如何构建一个具有类似银行电子签名功能的Android应用,包括核心技术和实现步骤。 首先,理解“电子签名”的概念至关重要。电子签名是一种通过数字方式表示的签名形式,它能够提供与传统手写签名同等...

    android mupdf droidtext0.5 PDF手写签名

    DroidText0.5则是一个专为Android设计的文本渲染库,特别适用于在PDF上进行手写签名。这个库允许用户在屏幕上自由绘制,然后将这些绘制的笔迹转换为高质量的矢量图形,从而实现逼真的手写签名效果。DroidText0.5的...

    android 手写、涂鸦demo

    这个"android 手写、涂鸦demo"是一个示例项目,展示了如何在Android应用程序中实现自定义的手绘功能。以下是对这个Demo涉及知识点的详细解释: 1. **画布(Canvas)**:在Android中,Canvas是用于绘制图形的基础类。...

    android手写轨迹保存(防有道云笔记手写输入)

    在Android开发中,实现手写轨迹保存功能是一个比较复杂但极具实用价值的技术点。这个"android手写轨迹保存(防有道云笔记手写输入)"的项目,旨在提供一个能够记录用户手写输入并进行编辑的示例。下面将详细阐述这个...

    android的手写功能

    如果需要实现更复杂的手写输入功能,如汉字识别,可以创建一个继承自`InputMethodService`的自定义输入法服务。通过重写`onCreateInputSession()`方法,创建一个`InputSession`,并在其中处理触控事件并进行识别。 ...

    支持笔锋效果的安卓手写签字控件

    Android平台原生手写签字控件,功能强大的开源项目,支持空白画布手写,田字格逐字手写,撤销/恢复,橡皮擦,画笔设置等功能,支持笔锋效果。 Android 手写签批 画板 笔锋。 Android应用源码PDF手写签名商业源码。 ...

    android手写输入

    总的来说,“android手写输入”结合了先进的计算机视觉和人工智能技术,为用户提供了一个高效、自然的输入方式。无论是日常沟通还是学习汉字,Android手写输入都极大地丰富了用户的交互体验。随着技术的发展,我们...

    android 便签 手写签到demo

    "android 便签 手写签到demo"是一个专门为开发者准备的示例项目,它展示了如何在Android应用中实现手写签到功能,类似于小米手机内置的便签应用。这个demo对于想要学习和实现类似功能的开发者来说,具有很高的参考...

    android手写签名保存

    在手写签名中,我们需要创建一个`Path`对象来跟踪用户的笔触轨迹。 2. **MotionEvent**: `MotionEvent`事件用于处理触摸屏幕时的各种动作,包括按下、移动和抬起等。在手写签名中,我们需要监听这些事件来获取...

    android 手写绘画支持权限获取

    在Android平台上实现手写绘画功能,开发者通常会利用触摸屏事件和绘图API来创建一个用户可以自由绘制的界面。这个过程涉及到多个知识点,包括触摸事件处理、绘图API的使用以及权限管理。以下是关于"android 手写绘画...

Global site tag (gtag.js) - Google Analytics