1.写这边文章的目的:从实习到现在两年的时间,在这段时间内学习和播放器打交道的时间算是比较多然鹅在做新的项目的时候居然发现自己还是必须重新再写一遍以前的代码,重复的干同样的一件事。感觉这样十分浪费时间,希望能封装一个比较灵活的播放器代码以后只需要集成添加对应的业务逻辑即可。因此参照着以前项目上视频播放器的东西。想要自己封装一个比较通俗易用的音视频播放模块。希望这个模块能够达到以下的功能。
a.整个模块不接触具体的业务逻辑,能够根据具体的项目需求实现定制化的功能
b.能实现播放器的任意切换(很有可能有的项目使用MediaPlayer有的使用IJK)
c.对于音频播放器而言能够定制自己的notification,能够在现在的设计下进行交互处理。
2.模块设计:
具体介绍:
AbsMusicPlayerService:后台音乐播放服务,持有逻辑控制器controller和InotificationStrage并且实现了音乐播放的事件监听IMusicPlayerEvents。
AbsPlayerController:封装了播放器相关的逻辑,具体的特殊业务逻辑需要自己在实现类中进行处理。
MusicBroadcastReceive:主要是接收notification的PendingIntent发送过来与音频相关的广播。
AbsNotification:根据不同的状态构建不同的notification通知。
AndroidMusicPlayer 实现了IMusicPlayer:音乐播放器接口抽象,封装了mediaPlayer实现音乐播放。
先看看效果图:
使用:
(1).在自己项目build.gradle中添加 implementation'com.txl.player:player:1.0.1'
(2).继承AbsPlayerController实现自己的业务逻辑,在具体的实现中决定使用什么类型的播放器,和如何创建对应的nitification。
package com.txl.demo; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.support.v4.app.NotificationCompat; import android.widget.RemoteViews; import com.txl.page.MainActivity; import com.txl.player.music.AbsPlayerController; import com.txl.player.music.AbsNotificationFactory; import com.txl.player.music.PlayerTag; import com.txl.player.android.player.R; import java.util.ArrayList; import java.util.List; /** * @author TXL * description : */ public class DemoMusicPlayerController extends AbsPlayerController { private static final int REQUEST_CODE = 1568; List<MusicData> musicData; int currentPlayIndex = 0; public static final String ACTION_PLAY_NEXT = "play_next"; public static final String ACTION_PLAY_PRE = "play_pre"; public DemoMusicPlayerController(Context context) { super(context); } @Override public AbsNotificationFactory createMediaNotificationManager(Context context) { return new DemoNotificationFactory(context); } @Override public void init(Intent initParams) { prepareMusicData(); //原来的 PlayerTag lastPlayerTag = getPlayTag(); PlayerTag tag = new PlayerTag(musicData.get( currentPlayIndex ).getPlayUrl()); //前后播放的不是同一个切换播放源 if(!tag.equals(lastPlayerTag)){ open(musicData.get( currentPlayIndex ).getPlayUrl(),tag); onReceiveControllerCommand(ACTION_PLAY_NEXT, musicData.get( currentPlayIndex )); } } private void prepareMusicData(){ musicData = new ArrayList<>( ); //需要自己处理播放地址,这个地址仅限当天有效 musicData.add( new MusicData(R.drawable.music_author_01,"生僻字","http://fs.w.kugou.com/201903171123/dadac9f0a31323d89246309adcecb5c5/G111/M06/1D/10/D4cBAFoL9VyASCmXADTAFw14uaI428.mp3") ); musicData.add( new MusicData(R.drawable.music_author_02,"一曲相思","http://fs.w.kugou.com/201903171124/91a30e730f830675816c05bba1f6707a/G085/M07/0B/10/lQ0DAFujV42AK4xpACkHR2d9qTo587.mp3") ); } @Override public void playNext() { currentPlayIndex ++; currentPlayIndex = currentPlayIndex % musicData.size(); PlayerTag tag = new PlayerTag(musicData.get( currentPlayIndex ).getPlayUrl()); open( musicData.get( currentPlayIndex ).getPlayUrl(),tag ); onReceiveControllerCommand(ACTION_PLAY_NEXT, musicData.get( currentPlayIndex )); play(); } @Override public void playPre() { if(currentPlayIndex == 0){ currentPlayIndex = musicData.size()-1; }else { currentPlayIndex--; } PlayerTag tag = new PlayerTag(musicData.get( currentPlayIndex ).getPlayUrl()); setPlayTag(tag); open(musicData.get( currentPlayIndex ).getPlayUrl()); onReceiveControllerCommand(ACTION_PLAY_PRE, musicData.get( currentPlayIndex )); play(); } @Override public void setPlayMode(int mode) { } @Override public void receiveCommand(String action, Object... o) { } /** * @author TXL * description : */ public class DemoNotificationFactory extends AbsNotificationFactory { public DemoNotificationFactory(Context context) { super(context); } @Override public Notification createPlayNotification() { if (isAndroidOOrHigher()) { createChannel(); } final NotificationCompat.Builder builder = new NotificationCompat.Builder( mContext, CHANNEL_ID); final RemoteViews normalRemoteViews = new RemoteViews( mContext.getPackageName(),R.layout.normal_notification); normalRemoteViews.setImageViewResource(R.id.image_icon,musicData.get(currentPlayIndex).playerUserIconResId); normalRemoteViews.setImageViewResource(R.id.ib_toggle,R.drawable.image_pause); Intent intent = new Intent(); String packageName = mContext.getPackageName(); intent.setAction(packageName+MusicBroadcastReceiver.ACTION_PAUSE); PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,REQUEST_CODE,intent,PendingIntent.FLAG_CANCEL_CURRENT); normalRemoteViews.setOnClickPendingIntent(R.id.ib_toggle, pendingIntent); intent = new Intent(); intent.setAction(packageName+MusicBroadcastReceiver.ACTION_PLAY_PRE); normalRemoteViews.setOnClickPendingIntent(R.id.ib_play_pre, PendingIntent.getBroadcast(mContext,REQUEST_CODE,intent,PendingIntent.FLAG_CANCEL_CURRENT)); intent = new Intent(); intent.setAction(packageName+MusicBroadcastReceiver.ACTION_PLAY_NEXT); normalRemoteViews.setOnClickPendingIntent(R.id.ib_play_next, PendingIntent.getBroadcast(mContext,REQUEST_CODE,intent,PendingIntent.FLAG_CANCEL_CURRENT)); normalRemoteViews.setTextViewText(R.id.tv_audio_title,musicData.get(currentPlayIndex).musicName); builder .setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) .setVibrate(new long[]{0}) .setSound(null) .setCustomContentView(normalRemoteViews) .setSmallIcon(R.drawable.easy_player_icon) .setShowWhen(false) .setColor(Color.RED)//可以主动设置 .setContentIntent(createContentIntent()) // Show controls on lock screen even when user hides sensitive content. .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); return builder.build(); } @Override public Notification createPauseNotification() { if (isAndroidOOrHigher()) { createChannel(); } final NotificationCompat.Builder builder = new NotificationCompat.Builder( mContext, CHANNEL_ID); final RemoteViews normalRemoteViews = new RemoteViews( mContext.getPackageName(),R.layout.normal_notification); normalRemoteViews.setImageViewResource(R.id.ib_toggle,R.drawable.image_play); normalRemoteViews.setOnClickPendingIntent(R.id.ib_toggle, createPauseIntent()); normalRemoteViews.setImageViewResource(R.id.image_icon,musicData.get(currentPlayIndex).playerUserIconResId); String packageName = mContext.getPackageName(); Intent intent = new Intent(); intent.setAction(packageName+MusicBroadcastReceiver.ACTION_PLAY_PRE); normalRemoteViews.setOnClickPendingIntent(R.id.ib_play_pre, PendingIntent.getBroadcast(mContext,REQUEST_CODE,intent,PendingIntent.FLAG_CANCEL_CURRENT)); intent = new Intent(); intent.setAction(packageName+MusicBroadcastReceiver.ACTION_PLAY_NEXT); normalRemoteViews.setOnClickPendingIntent(R.id.ib_play_next, PendingIntent.getBroadcast(mContext,REQUEST_CODE,intent,PendingIntent.FLAG_CANCEL_CURRENT)); normalRemoteViews.setTextViewText(R.id.tv_audio_title,musicData.get(currentPlayIndex).musicName); builder .setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) .setVibrate(new long[]{0}) .setSound(null) .setCustomContentView(normalRemoteViews) .setSmallIcon(R.drawable.easy_player_icon) .setShowWhen(false) .setColor(Color.RED)//可以主动设置 .setContentIntent(createContentIntent()) // Show controls on lock screen even when user hides sensitive content. .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); return builder.build(); } private PendingIntent createContentIntent() { Intent intent = new Intent(mContext, MainActivity.class); return PendingIntent.getActivity(mContext,REQUEST_CODE,intent,PendingIntent.FLAG_CANCEL_CURRENT); } private PendingIntent createPauseIntent(){ Intent intent = new Intent(); String packageName = mContext.getPackageName(); intent.setAction(packageName+MusicBroadcastReceiver.ACTION_PLAY); return PendingIntent.getBroadcast(mContext,REQUEST_CODE,intent,PendingIntent.FLAG_CANCEL_CURRENT); } @Override public Notification createSeekNotification(long pos) { return null; } @Override public Notification createOtherNotification(String action, Object... o) { return null; } } }
(3)实现自己的音频播放服务,并将它注册在manifest
public class AndroidMusicPlayerService extends AbsMusicPlayerService { @Override protected IMusicPlayerController createPlayerController() { return new DemoMusicPlayerController(this); } @Override public boolean onReceiveControllerCommand(String action, Object... o) { notificationManager.createOtherNotification(action,o); return false; } }
(4)在需用用到音频播放器的地方实现绑定对应的服务,获取对应的播放控制器实现逻辑控制。
public class MainActivity extends AppCompatActivity implements IMusicPlayer.IMusicPlayerEvents{ protected final String TAG = getClass().getSimpleName(); IMusicPlayerController musicPlayerController; /** * 名字 * */ TextView tvMusicName; ImageView imagePreMusic; ImageView imageNextMusic; ImageView imageToggleMusic; ImageView imageMusicAuthorIcon; SeekBar seekBar; long duration = 0; ServiceConnection serviceConnection; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate( savedInstanceState ); setContentView( R.layout.activity_main ); initView(); Intent intent = new Intent(); intent.setClass(this,AndroidMusicPlayerService.class); serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { if(service instanceof IMusicPlayerController){ musicPlayerController = (IMusicPlayerController) service; musicPlayerController.addPlayerEventListener(MainActivity.this); //在使用之前需要初始化一下 musicPlayerController.init(null); }else { Log.e(TAG,"serviceConnection musicPlayerController init error unBind service"); } } @Override public void onServiceDisconnected(ComponentName name) { musicPlayerController.removePlayerEventListener(MainActivity.this); } }; bindService(intent, serviceConnection,Context.BIND_AUTO_CREATE); } private void initView() { tvMusicName = findViewById( R.id.tv_music_name ); imageMusicAuthorIcon = findViewById( R.id.image_music_author ); imagePreMusic = findViewById( R.id.image_pre_music ); imagePreMusic.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { seekBar.setProgress(0); musicPlayerController.playPre(); } } ); imageNextMusic = findViewById( R.id.image_next_music ); imageNextMusic.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { seekBar.setProgress(0); musicPlayerController.playNext(); } } ); imageToggleMusic = findViewById( R.id.image_toggle_music ); imageToggleMusic.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { if(musicPlayerController.isPlaying()){ musicPlayerController.pause(); }else { musicPlayerController.play(); } } } ); seekBar = findViewById(R.id.music_seek_bar); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { Log.d(TAG,"onProgressChanged progress :"+progress+" fromUser: "+fromUser); } @Override public void onStartTrackingTouch(SeekBar seekBar) { Log.d(TAG,"onStartTrackingTouch "); } @Override public void onStopTrackingTouch(SeekBar seekBar) { Log.d(TAG,"onStopTrackingTouch "); if(duration != 0){ long pos = seekBar.getProgress()*1000; musicPlayerController.seek(pos); } } }); } private void togglePlayerUi(boolean isPlay){ if(isPlay){ imageToggleMusic.setImageResource( R.drawable.image_pause ); }else { imageToggleMusic.setImageResource( R.drawable.image_play ); } } @Override protected void onDestroy() { super.onDestroy(); unbindService(serviceConnection); musicPlayerController = null; } private void changePageUi(MusicData musicData){ tvMusicName.setText(musicData.getMusicName()); imageMusicAuthorIcon.setImageResource(musicData.getPlayerUserIconResId()); } @Override public boolean onError(IMusicPlayer xmp, int code, String msg) { togglePlayerUi(false); return false; } @Override public boolean onPrepared(IMusicPlayer player) { return false; } @Override public boolean onSeekComplete(IMusicPlayer player, long pos) { return false; } @Override public boolean onComplete(IMusicPlayer player) { togglePlayerUi(false); return false; } @Override public boolean onBuffering(IMusicPlayer player, boolean buffering, float percentage) { return false; } @Override public boolean onProgress(IMusicPlayer player, long pos, long total) { if(total != 0){ duration = total/1000; seekBar.setMax((int) (total/1000)); seekBar.setProgress((int) (pos/1000)); }else { duration = musicPlayerController.getDuration(); } return false; } @Override public void onMusicServiceDestroy(IMusicPlayer player) { } @Override public boolean onPlay(IMusicPlayer player) { togglePlayerUi(true); return false; } @Override public boolean onPause(IMusicPlayer player) { togglePlayerUi(false); return false; } @Override public boolean onStop(IMusicPlayer player) { togglePlayerUi(false); return false; } @Override public boolean onReceiveControllerCommand(String action, Object... o) { switch (action){ case DemoMusicPlayerController.ACTION_PLAY_NEXT: case DemoMusicPlayerController.ACTION_PLAY_PRE: if(o != null && o.length > 0){ MusicData musicData = (MusicData) o[0]; changePageUi(musicData); } break; default: break; } return false; } @Override public void onBackPressed() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addCategory(Intent.CATEGORY_HOME); startActivity(intent); } }
这样一款简单的音乐播放器就实现了。
最后附上demo地址。https://github.com/xiaolutang/AndroidPlayer
2019第的以一个flag:继续完成封装通用的视频播放代码。还有,2018年末在鸿洋大神的wanandroid上面看了许多的2018的年度总结,希望2019我也要有一个漂亮的年度总计。