本章深入剖析音频播放器Service的关键实现,通过精选代码片段详解权限配置、播放控制、状态管理等核心功能模块。

播放服务必须声明前台服务权限
name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
name="android.permission.FOREGROUND_SERVICE" />
1、服务核心变量定义
private static AudioPlayerManager apm; //音频播放管理
private static int mPosSomeTime = 0;// 播放指针 单元
private String jsonList, unit_name; //歌曲列表数据 歌曲名称
private static int bookId; //内层专辑ID
private static String book_name; //内层专辑ID
private static long process = 0; //当前歌曲播放进度
private static long max = 0; //当前歌曲最大进度
private static ExecutorService fixedThreadPool;
private static int isFrom; //来源 0 专辑列表 1首页 2 AI问答
private String book_cover; //专辑封面
private static int bizId; //外层专辑ID
private static String bizTopic; //外层专辑主题
private static long timeStart = 0; //开始计时的时间戳
private static boolean countDowning = false; //是否倒计时中
2、服务初始化流程
@Override
public void onCreate() {
super.onCreate();
EventBus.getDefault().register(this);
fixedThreadPool = Executors.newFixedThreadPool(3);
apm = AudioPlayerManager.getInstance(TbjcPlayerService.this); Player.Listener listener = new Player.Listener() {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
switch (playbackState) {
case Player.STATE_BUFFERING://加载缓存且还未准备好时触发 break;
case Player.STATE_READY: //播放准备
timeStart = System.currentTimeMillis(); //记录播放开始时间
break;
case Player.STATE_ENDED: //播放结束时触发
process=0;
fixedThreadPool.execute(command4); //保存播放进度
int sum = 0;
sum = ConstantFd.unit_List.size(); //专辑中的歌曲数量
if(ConstantFd.loop_type == 2 && sum > 0){ //随机播放
int index = (int) (Math. random() * sum-1);
to_play(index);
}else if(ConstantFd.loop_type == 0){ //顺序播放
Log.e("TAG", "mPosSomeTime---> "+mPosSomeTime);
if(mPosSomeTime < sum - 1){//播放指针 小于 列表总数
play_next(1); //播放下一个
}else { //已播完最后一首 暂停操作
ConstantFd.isplay = false;
apm.playOrPause(false);
PlayStateMsg.sendState(false); //发送播放状态 通知ui
}
}else if(ConstantFd.loop_type == 1){ //单课循环
Log.e("TAG", "单课循环---> 重新开始");
playUrl = ConstantFd.unit_List.get(mPosSomeTime).getPlayUrl();
apm.setAudioFile2(playUrl); //在线
apm.start();
}
break;
} }
};
apm.setlistener(listener); //设置播放
apm.switchSpeed(ConstantFd.speed); //设置播放倍速
//设置网络
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(networkReceiver, filter, Context.RECEIVER_EXPORTED);
}else {
registerReceiver(networkReceiver, filter);
}
}
3、数据加载与播放控制
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
book_cover = intent.getStringExtra("book_cover");
book_name = intent.getStringExtra("book_name");
bookId = intent.getIntExtra("bookId", 0);
jsonList = intent.getStringExtra("jsonList");
isFrom = intent.getIntExtra("isFrom", 0);
bizId = intent.getIntExtra("bizId", 0);
unit_name = intent.getStringExtra("unit_name");
bizTopic = intent.getStringExtra("bizTopic");
Log.i("TAG", "jsonList : " + jsonList);
}else {
Log.e("TAG", "intent null---> ");
}
apm.requestAudioFocus(); //请求音频播放焦点
if(!TextUtils.isEmpty(jsonList)){ //解析列表数据
unit_List = new Gson().fromJson(jsonList, new TypeToken>() { }.getType());
startPlay(); //开始播放
}else {
reLoad();
}
if (!countDowning) {
SharedPreferences sharedPreferences = getSharedPreferences(ConstantFd.PREFERENCES, MODE_PRIVATE); //上次定时关闭
int time = sharedPreferences.getInt(ConstantFd.LastTimeSet, 0);
if(time > 0){
showSurplusTime = true; //显示倒计时
countDown(time * 60 * 1000);
}else {
showSurplusTime = false;
}
}else {
ConstantFd.showSurplusTime = true;
if (ConstantFd.surplusTime > 0) {
mHandler.removeMessages(2);
mHandler.sendEmptyMessageDelayed(2, 1000);
}
}
return START_NOT_STICKY;
} private void reLoad(){ //加载播放
isplay = false; //播放状态设为false
if (!TextUtils.isEmpty(book_name) && bookId >= 0) {
fixedThreadPool.execute(command1); //使用线程池避免播就了之后卡顿
}
}private void countDown(long time) { //设置时间 开始到计时
Log.i("TAG", "ConstantFd.surplusTime: "+ConstantFd.surplusTime);
ConstantFd.surplusTime = time;
ConstantFd.setTime = time;
mHandler.removeMessages(2);
if (ConstantFd.surplusTime > 0) {
countDowning = true;
mHandler.sendEmptyMessageDelayed(2, 1000);
} else {
countDowning = false;
} }
4、播放控制方法实现
//开始播放
private void startPlay(){
PlayInfoMsg.sendInfo(true); //发送消息 通知UI刷新列表和进度条
mPosSomeTime = getPoi(unit_name);
if(apm.isPlaying() && isFrom==1){ //如果是播放中 继续播放,做到从首页跳转过来无缝衔接播放
PlayShowPoiMsg.sendPoi(mPosSomeTime);
PlayStateMsg.sendState(true);
}else {
if(!TextUtils.isEmpty(unit_name)){ //从列表页传来的单元名称
PlayUnitPoiMsg.sendPoi(mPosSomeTime, book_cover);
}else {
fixedThreadPool.execute(command3); //获取上次播放的单元指针
}
}
Log.i("TAG", "111 isplay: "+ConstantFd.isplay);
Log.i("TAG", "111 control_play: "+ConstantFd.control_play);
if(!ConstantFd.control_play){ //外面手动暂停 进播放页暂停
PlayControlTools.setACTION_PAUSE();
}
} private static void setPlayPoi(long poi){ //播放当前歌曲中的指定位置
apm.getMediaPlayer().seekTo(poi);
}private void play_next(int LeftorRight) { //播放下一首 0上一个 1下一个
if(!ConstantFd.hasList()){ //列表无数据
return;
}
int position = mPosSomeTime; //当前单元位置
if (position == -1) {
return;
}
int sum = 0;
sum = ConstantFd.unit_List.size();
if (LeftorRight == 0) { //左
if (position > 0) {
position--;
} else {
position = sum - 1;
}
} else if (LeftorRight == 1) { //右
if (position < sum - 1) {
position++;
} else {
position = 0;
}
}
to_play(position); } private void to_play(int position) { //播放列表中的哪一首歌
listenUpload();
mPosSomeTime = position;
PlayUnitPoiMsg.sendPoi(mPosSomeTime, book_cover); //发送播放指针 并播放
}
5、消息通信处理机制
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void receiveTime(PlayTimeSetMsg msg) { //接收设置的定时时间
countDown(msg.getTime());
} @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void receiveProcess(PoiToPlayMsg msg) { //接收滑动的进度
setPlayPoi(msg.getPoi());
} @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void receiveSet(PlaySetMsg msg) { //播放设置倍速
apm.switchSpeed(ConstantFd.speed);
} @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void receiveEventBus_Poi(PlayUnitPoiMsg msg) { //接收指针 播这一首
mPosSomeTime = msg.getPoi();
ConstantFd.isplay = true;
PlayStateMsg.sendState(true);
playStart();
} @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void receiveEventBus(PlayControlMsg msg) { //接收消息控制播放
if (msg.getMsg().equals(PlayControlMsg.YYTS_ACTION_PAUSE)) { //暂停
playOrPause(false);
PlayStateMsg.sendState(false); //发送播放状态 通知ui
} else if (msg.getMsg().equals(PlayControlMsg.YYTS_ACTION_PLAY)) { //播放
if(apm.getMediaPlayer().getCurrentPosition() >= max){
setPlayPoi(0);
}
playOrPause(true);
PlayStateMsg.sendState(true); //发送播放状态 通知ui
} else if (msg.getMsg().equals(PlayControlMsg.YYTS_PLAY_NEXT)) { //下一首
int sum = 0;
sum = ConstantFd.unit_List.size();
if (ConstantFd.loop_type == 2 && sum > 0) { //随机播放
int index = (int) (Math.random() * sum - 1);
to_play(index);//播放指定位置歌曲
} else {
play_next(1);//播放下一首
}
} else if (msg.getMsg().equals(PlayControlMsg.YYTS_PLAY_PREVIOUS)) { //上一首
int sum = 0;
sum = ConstantFd.unit_List.size();
if (ConstantFd.loop_type == 2 && sum > 0) { //随机播放
int index = (int) (Math.random() * sum - 1);
to_play(index); //播放指定位置歌曲
} else {
play_next(0); //播放上一首
}
}
}
6、线程任务与播放进度管理
private static Handler mHandler = new Handler(new Handler.Callback() { //static 定时
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 0: //获取当前进度并发给ui
max = apm.getMediaPlayer().getDuration();
process = apm.getMediaPlayer().getCurrentPosition();
if(max<0){
max=0;
}
PoiToUIMsg.sendMsg(process, max); //发送进度到UI
if(ConstantFd.isplay){
mHandler.sendEmptyMessageDelayed(0, 1000);
}
fixedThreadPool.execute(command4); //保存播放进度
break;
case 1:
setPlayPoi(msg.arg1);//设置上次进度
break;
case 2: //定时播放 倒计时
ConstantFd.surplusTime = ConstantFd.surplusTime - 1000;
if (ConstantFd.showSurplusTime) {
PlayTimeShowMsg.sendMsg(ConstantFd.surplusTime);
}
if (ConstantFd.surplusTime > 0) {
if(ConstantFd.isplay){
mHandler.sendEmptyMessageDelayed(2, 1000); //每秒刷新
}
} else { //定时播放结束
ConstantFd.surplusTime = 0;
countDowning = false;
if (ConstantFd.setTime != 0) {
ConstantFd.isplay = false;
apm.playOrPause(false);
PlayStateMsg.sendState(false); //发送播放状态 通知ui
}
}
break;
}
return true;
}
});
private Runnable command1 = new Runnable() {
@Override
public void run() { //请求数据 播放列表
RequestMsg.sendInfo(true);
getDataList(); //获取播放数据
}
}; private Runnable command2 = new Runnable() { //获取上次进度 并保存本次单元指针
@Override
public void run() {
if(!ConstantFd.hasList() || isFrom==2){
return;
}
//获取上次播放进度
Message msg = new Message();
msg.what = 1;
msg.arg1 = (int) getSongProcess();
mHandler.sendMessage(msg); //保存播放歌曲指针
JxwRoom.getInstance().getJxwDatabase().getLastSongDao().deleteAllInfo();
LastSong song = new LastSong();
song.setSong_name(ConstantFd.unit_List.get(mPosSomeTime).getShowName());
song.setIndex(mPosSomeTime);
song.setBook_id(bookId);
song.setBook_name(book_name);
song.setSong_cover(book_cover);
song.setBiz_id(bizId);
JxwRoom.getInstance().getJxwDatabase().getLastSongDao().insertInfo(song);
AlbumSong as =JxwRoom.getInstance().getJxwDatabase().getAlbumSongDao().getSongPoi(book_name, bookId); //保存专辑播放到哪一曲
if(as!=null){
as.setSong_name(ConstantFd.unit_List.get(mPosSomeTime).getShowName());
as.setIndex(mPosSomeTime);
JxwRoom.getInstance().getJxwDatabase().getAlbumSongDao().updateInfo(as);
}else {
as = new AlbumSong();
as.setSong_name(ConstantFd.unit_List.get(mPosSomeTime).getShowName());
as.setIndex(mPosSomeTime);
as