尊龙凯时网址

android10.0如何实现本地音乐播放? -尊龙凯时网址

2024-01-24

这篇文章将为大家详细讲解有关android10.0如何实现本地音乐播放?,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

1.概述

本篇文章仅是android小白在写一个小程序,内容仅供参考,有很多不足之处希望各位大神指出,文章末尾有整个项目的下载,不需要币,只求帮你们解决到问题的同时收获到一颗小小的赞。这个项目中还有很多不足的地方,如:在按键中设置图片文字,这些正常的应该交给handler处理,我只是粗略地完成这个项目。测试环境:android10.0。实现:自动播放下一首,正常音乐的功能,全屏显示。
android10.0是内外分存了的,应用是没有权限读取内存的,需要在配置文件中application中加上属性:android:requestlegacyexternalstorage=“true”,不加可能可以读取歌曲,但是无法播放。

2.效果截图

截图显示不同是因为这不是同一时间截的,只是一个效果图

3.读取本地音乐以及保存歌曲

①先在androidmanifest文件里面配置权限


②目前基本上的手机使用静态权限是不够的,需要动态获取权限,因此需要在mainactivity里面动态获取,在oncreate方法里调用方法

private void check(){
 if (build.version.sdk_int >= build.version_codes.m) {
  if (checkselfpermission(manifest.permission.write_external_storage) != packagemanager.permission_granted ) {
   requestpermissions(new string[]{manifest.permission.write_external_storage}, 1);
   log.d(tag,"---------------------写权限不够-----------------");
  }
  if(checkselfpermission(manifest.permission.read_external_storage)!= packagemanager.permission_granted ){
   requestpermissions(new string[]{manifest.permission.read_external_storage}, 2);
   log.d(tag,"---------------------读权限不够-----------------");
  }
 }
}

③再去实现权限的回调方法,与activity的oncreate方法是同一级别的

@override
public void onrequestpermissionsresult(int requestcode, @nonnull string[] permissions, @nonnull int[] grantresults) {
 super.onrequestpermissionsresult(requestcode, permissions, grantresults);
 switch (requestcode) {
  case 1:
   if (grantresults.length > 0 && grantresults[0] == packagemanager.permission_granted) {
    log.d(tag, "---------------------写权限够了-----------------------------");
   }
   break;
  case 2:
   if (grantresults.length > 0 && grantresults[0] == packagemanager.permission_granted) {
    log.d(tag, "---------------------读权限够了-----------------------------");
   }
   break;
 }
}

④创建一个工具类mp3info,用来保存音乐信息的,里面主要是一些get和set方法

public class mp3info {
 private string url;//路径
 private string title;//歌曲名
 private string artist;//艺术家
 private long duration;//歌曲时长
 private long id;
 private long album;//专辑图片
 }

⑤创建一个musicutil类,通过contentporvider的接口获取歌曲信息

public class musicutil {
 //获取专辑封面的ui
 private static final string tag="musicutil";
 private static final uri albumarturi=uri.parse("content://media/external/audio/albumart");
 //生成歌曲列表
 public static list getmp3infolist(context context){
  cursor cursor=context.getcontentresolver().query(mediastore.audio.media.external_content_uri, null, null, null,null);
  list mp3infolist=new arraylist<>();
  while(cursor.movetonext()){
   mp3info mp3info=new mp3info();
   mp3info.set));//path
   mp3info.settitle(cursor.getstring(cursor.getcolumnindex(mediastore.audio.media.title)));
   mp3info.setartist(cursor.getstring(cursor.getcolumnindex(mediastore.audio.media.artist)));
   mp3info.setduration(cursor.getlong(cursor.getcolumnindex(mediastore.audio.media.duration)));
   mp3info.setid(cursor.getlong(cursor.getcolumnindex(mediastore.audio.media._id)));
   mp3info.setalbum(cursor.getint(cursor.getcolumnindex(mediastore.audio.media.album_id)));
   mp3infolist.add(mp3info);
  }
  return mp3infolist;
 }
 //格式化时间,转换为分/秒
 public static string formattime(long time){
  string min = time / (1000 * 60)   "";
  string sec = time % (1000 * 60)   "";
  if (min.length() < 2) {
   min = "0"   time / (1000 * 60)   "";
  } else {
   min = time / (1000 * 60)   "";
  }
  if (sec.length() == 4) {
   sec = "0"   (time % (1000 * 60))   "";
  } else if (sec.length() == 3) {
   sec = "00"   (time % (1000 * 60))   "";
  } else if (sec.length() == 2) {
   sec = "000"   (time % (1000 * 60))   "";
  } else if (sec.length() == 1) {
   sec = "0000"   (time % (1000 * 60))   "";
  }
  return min   ":"   sec.trim().substring(0, 2);
 }
 //获取专辑图片,目前是只能获取手机自带歌曲的专辑图片,如果手机有酷狗,qq音乐之类的,可能无法获取专辑图片
 //因为他们的uri不知道。
 public bitmap getartwork(context context, long song_id, long album_id, boolean allowdefalut, boolean small){
  if(album_id < 0) {
   if(song_id < 0) {
    bitmap bm = getartworkfromfile(context, song_id, -1);
    if(bm != null) {
     return bm;
    }
   }
   if(allowdefalut) {
    return getdefaultartwork(context, small);
   }
   return null;
  }
  contentresolver res = context.getcontentresolver();
  uri uri = contenturis.withappendedid(albumarturi, album_id);
  if(uri != null) {
   inputstream in = null;
   try {
    in = res.openinputstream(uri);
    bitmapfactory.options options = new bitmapfactory.options();
    //先制定原始大小
    options.insamplesize = 1;
    //只进行大小判断
    options.injustdecodebounds = true;
    //调用此方法得到options得到图片的大小
    bitmapfactory.decodestream(in, null, options);
    /** 我们的目标是在你n pixel的画面上显示。 所以需要调用computesamplesize得到图片缩放的比例 **/
    /** 这里的target为800是根据默认专辑图片大小决定的,800只是测试数字但是试验后发现完美的结合 **/
    if(small){
     options.insamplesize = computesamplesize(options, 40);
    } else{
     options.insamplesize = computesamplesize(options, 600);
    }
    // 我们得到了缩放比例,现在开始正式读入bitmap数据
    options.injustdecodebounds = false;
    options.indither = false;
    options.inpreferredconfig = bitmap.config.argb_8888;
    in = res.openinputstream(uri);
    return bitmapfactory.decodestream(in, null, options);
   } catch (filenotfoundexception e) {
    bitmap bm = getartworkfromfile(context, song_id, album_id);
    if(bm != null) {
     if(bm.getconfig() == null) {
      bm = bm.copy(bitmap.config.rgb_565, false);
      if(bm == null && allowdefalut) {
       return getdefaultartwork(context, small);
      }
     }
    } else if(allowdefalut) {
     bm = getdefaultartwork(context, small);
    }
    return bm;
   } finally {
    try {
     if(in != null) {
      in.close();
     }
    } catch (ioexception e) {
     e.printstacktrace();
    }
   }
  }
  return null;
 }
 /**
  * 从文件当中获取专辑封面位图
  * @param context
  * @param songid
  * @param albumid
  * @return
  */
 private static bitmap getartworkfromfile(context context, long songid, long albumid){
  bitmap bm = null;
  if(albumid < 0 && songid < 0) {
   throw new illegalargumentexception("---------------------" tag "must specify an album or a song id");
  }
  try {
   bitmapfactory.options options = new bitmapfactory.options();
   filedescriptor fd = null;
   if(albumid < 0){
    uri uri = uri.parse("content://media/external/audio/media/"   songid   "/albumart");
    parcelfiledescriptor pfd = context.getcontentresolver().openfiledescriptor(uri, "r");
    if(pfd != null) {
     fd = pfd.getfiledescriptor();
    }
   } else {
    uri uri = contenturis.withappendedid(albumarturi, albumid);
    parcelfiledescriptor pfd = context.getcontentresolver().openfiledescriptor(uri, "r");
    if(pfd != null) {
     fd = pfd.getfiledescriptor();
    }
   }
   options.insamplesize = 1;
   // 只进行大小判断
   options.injustdecodebounds = true;
   // 调用此方法得到options得到图片大小
   bitmapfactory.decodefiledescriptor(fd, null, options);
   // 我们的目标是在800pixel的画面上显示
   // 所以需要调用computesamplesize得到图片缩放的比例
   options.insamplesize = 100;
   // 我们得到了缩放的比例,现在开始正式读入bitmap数据
   options.injustdecodebounds = false;
   options.indither = false;
   options.inpreferredconfig = bitmap.config.argb_8888;
   //根据options参数,减少所需要的内存
   bm = bitmapfactory.decodefiledescriptor(fd, null, options);
  } catch (filenotfoundexception e) {
   e.printstacktrace();
  }
  return bm;
 }
 /**
  * 获取默认专辑图片
  * @param context
  * @return
  */
 @suppresslint("resourcetype")
 public static bitmap getdefaultartwork(context context, boolean small) {
  bitmapfactory.options opts = new bitmapfactory.options();
  opts.inpreferredconfig = bitmap.config.rgb_565;
  if(small){ //返回小图片
   //return
   bitmapfactory.decodestream(context.getresources().openrawresource(r.drawable.default_picture), null, opts);
  }
  return bitmapfactory.decodestream(context.getresources().openrawresource(r.drawable.default_picture), null, opts);
 }
 /**
  * 对图片进行合适的缩放
  * @param options
  * @param target
  * @return
  */
 public static int computesamplesize(bitmapfactory.options options, int target) {
  int w = options.outwidth;
  int h = options.outheight;
  int candidatew = w / target;
  int candidateh = h / target;
  int candidate = math.max(candidatew, candidateh);
  if(candidate == 0) {
   return 1;
  }
  if(candidate > 1) {
   if((w > target) && (w / candidate) < target) {
    candidate -= 1;
   }
  }
  if(candidate > 1) {
   if((h > target) && (h / candidate) < target) {
    candidate -= 1;
   }
  }
  return candidate;
 }
}

⑥为列表设置adapter,新建一个myadapter类继承baseadapter,然后在重写的getview里面设置显示的控件

@override
public view getview(int position, view convertview, viewgroup parent) {
 if(convertview==null){
  holder=new viewholder();
  convertview=view.inflate(context, r.layout.list_item,null);
  holder.tv_title=convertview.findviewbyid(r.id.tv_title);
  holder.tv_artist=convertview.findviewbyid(r.id.tv_artist);
  holder.tv_duration=convertview.findviewbyid(r.id.tv_duration);
  holder.tv_position=convertview.findviewbyid(r.id.tv_position);
  convertview.settag(holder);
 }else {
  holder= (viewholder) convertview.gettag();
 }
 holder.tv_title.settext(list.get(position).gettitle());
 holder.tv_artist.settext(list.get(position).getartist());
 long duration = list.get(position).getduration();
 string time= musicutil.formattime(duration);
 holder.tv_duration.settext(time);
 holder.tv_position.settext(position 1 "");
 if(currentitem == position){
  holder.tv_title.setselected(true);
  holder.tv_position.setselected(true);
  holder.tv_duration.setselected(true);
  holder.tv_artist.setselected(true);
 }else{
  holder.tv_title.setselected(false);
  holder.tv_position.setselected(false);
  holder.tv_duration.setselected(false);
  holder.tv_artist.setselected(false);
 }
 return convertview;
}
class viewholder{
 textview tv_title;//歌曲名
 textview tv_artist;//歌手
 textview tv_duration;//时长
 textview tv_position;//序号
}

4.使用service实现后台播放

使用的是bindservice,这样service的生命周期就和activity的生命周期绑定在一起了。创建一个musicservice。注意:销毁service的时候需要将音乐对象release。

①service实现功能,在onbind方法里面实例化音乐播放对象

@override
public ibinder onbind(intent intent) {
 log.d(tag,"onbind is call");
 mybinder=new mybinder();
 return mybinder;
}

②在mybinder()里面实现音乐的各种功能,使用的是内部类,初始化部分请看源代码包

public class mybinder extends binder{
 private int index=0;//歌曲索引
 //播放音乐
 public void playmusic(int index){
  this.index=index;
  try {
   file file=new file(list.get(this.index).get);
   if(!file.exists()){
    log.d(tag,"------------------------------文件不存在------------------------------");
    return ;
   }else{
    log.d(tag,"------------------------------文件:" file.getpath() "存在 ------------------------------");
   }
   if(mediaplayer!=null){
    mediaplayer.reset();
    mediaplayer.release();
   }
   mediaplayer=new mediaplayer();
   string str=list.get(this.index).get;
   mediaplayer.setdatasource(str);
   log.d(tag,list.get(this.index).get "");
   mediaplayer.prepare();
   mediaplayer.start();
  } catch (ioexception e) {
   e.printstacktrace();
  }
 }
 //暂停音乐
 public void pausemusic(){
  if(mediaplayer.isplaying()){
   mediaplayer.pause();
  }
 }
 //关闭音乐
 public void closemusic(){
  if(mediaplayer!=null){
   mediaplayer.release();
  }
 }
 //下一首
 public void nextmusic(){
  if(index>=list.size()-1){
   this.index=0;
  }else{
   this.index =1;
  }
  playmusic(this.index);
 }
 //上一首
 public void preciousmusic(){
  if(index<=0){
   this.index=list.size()-1;
  }else{
   this.index-=1;
  }
  playmusic(this.index);
 }
 //获取歌曲时长
 public int getprogress(int dex){
  return (int)list.get(dex).getduration();
 }
 public int getprogress(){
  return (int)list.get(index).getduration();
 }
 //获取当前播放位置
 public int getplayposition(){
  return mediaplayer.getcurrentposition();
 }
 //移动到当前点播放
 public void seektoposition(int m){
  mediaplayer.seekto(m);
 }
}

③在mainactivity里面绑定
a.先实例化一个serviceconnection对象

private serviceconnection connection=new serviceconnection() {
 @override
 public void onserviceconnected(componentname name, ibinder service) {
  mybinder= (musicservice.mybinder) service;
  seekbar.setmax(mybinder.getprogress());
  seekbar.setonseekbarchangelistener(new seekbar.onseekbarchangelistener() {
   @override
   public void onprogresschanged(seekbar seekbar, int progress, boolean fromuser) {
    //这里是判断进度条移动是不是用户所为
    if(fromuser){
     mybinder.seektoposition(seekbar.getprogress());
    }
   }
   @override
   public void onstarttrackingtouch(seekbar seekbar) {
   }
   @override
   public void onstoptrackingtouch(seekbar seekbar) {
   }
  });
  handler.post(runnable);
  log.d(tag, "service与activity已连接");
 }
 @override
 public void onservicedisconnected(componentname name) {
 }
};

b.还需要一个handler来控制ui组件的变化,实例化放在了oncreate方法里面。
c.用一个runnable对象进行seekbar的前进

private runnable runnable=new runnable() {
 @override
 public void run() {
  seekbar.setprogress(mybinder.getplayposition());
  tv_lefttime.settext(time.format(mybinder.getplayposition()) "");
  tv_righttime.settext(time.format(mybinder.getprogress()-mybinder.getplayposition()) "");
  if(mybinder.getprogress()-mybinder.getplayposition()<1000){//时间不够了自动触发下一首
   runonuithread(new runnable() {//使用ui线程来触发按键点击事件,不知道这样有没有什么危害
    @override
    public void run() {
     ib_next.performclick();
    }
   });
  }
  handler.postdelayed(runnable,1000);
 }
};

d.在oncreate方法里进行绑定

mediaserviceintent =new intent(this,musicservice.class);//mediaserviceintent为一个intent
bindservice(mediaserviceintent,connection,bind_auto_create);

5.使用notification通知栏通知

注意::如果点击通知栏是从mainactivity跳转到mainactivity,需要在配置文件的activity android:name=".mainactivity"
android:launchmode=“singletask”,设置为单任务。
布局在源代码包里,在api26级以上需要使用notificationchannel
①设置通知所触发的pandingintent,通过action识别,action为自己定义的常量,setsound无声音。通过remoteviews去实现通知栏组件的按钮实现

//设置通知
private void setnotification(){
 string channelid="cary";
 if(build.version.sdk_int>=build.version_codes.o){
  notificationchannel channel=new notificationchannel(channelid,"xxx",notificationmanager.importance_low);
  manager.createnotificationchannel(channel);
 }
 intent intent=new intent(mainactivity.this,mainactivity.class);
 pendingintent pi=pendingintent.getactivity(mainactivity.this,0,intent,0);
 if (build.version.sdk_int >= build.version_codes.o) {
  notify=new notification.builder(mainactivity.this,channelid)
   .setwhen(system.currenttimemillis())
   .setsound(null)
   .build();
 }
 notify.icon=android.r.drawable.btn_star;
 notify.contentintent=pi;
 notify.contentview=remoteviews;
 notify.flags=notification.flag_ongoing_event;
 remoteviews.setonclickpendingintent(r.id.notice,pi);
 //上一首
 intent previntent=new intent(button_prev_id);
 pendingintent prevpendingintent=pendingintent.getbroadcast(this,0,previntent,0);
 remoteviews.setonclickpendingintent(r.id.widget_prev,prevpendingintent);
 //播放暂停
 intent playintent=new intent(button_play_id);
 pendingintent playpendingintent=pendingintent.getbroadcast(this,0,playintent,0);
 remoteviews.setonclickpendingintent(r.id.widget_play,playpendingintent);
 //下一首
 intent nextintent=new intent(button_next_id);
 pendingintent nextpendingintent=pendingintent.getbroadcast(this,0,nextintent,0);
 remoteviews.setonclickpendingintent(r.id.widget_next,nextpendingintent);
 //关闭
 intent closeintent=new intent(button_close_id);
 pendingintent closependingintent=pendingintent.getbroadcast(this,0,closeintent,0);
 remoteviews.setonclickpendingintent(r.id.widget_close,closependingintent);
}

②动态注册广播

//注册广播
private void initbuttonreceiver(){
 buttonbroadcastreceiver=new buttonbroadcastreceiver();
 intentfilter intentfilter=new intentfilter();
 intentfilter.addaction(button_prev_id);
 intentfilter.addaction(button_play_id);
 intentfilter.addaction(button_next_id);
 intentfilter.addaction(button_close_id);
 registerreceiver(buttonbroadcastreceiver,intentfilter);
}

③显示广播,需要注意的是,每次在activity里面点击上一首或者下一首都需要调用这个方法,刷新通知栏的标题,以及状态专辑

//展示通知
private void shownotification(){
 if(isplaying){
  remoteviews.setimageviewresource(r.id.widget_play,r.drawable.stop);
 }else{
  remoteviews.setimageviewresource(r.id.widget_play,r.drawable.start);
 }
 remoteviews.setimageviewbitmap(r.id.widget_album,utils.getartwork(mainactivity.this,list.get(music_index).getid(),list.get(music_index).getalbum(),true,false));
 remoteviews.setimageviewresource(r.id.widget_close,android.r.drawable.ic_menu_close_clear_cancel);
 remoteviews.settextviewtext(r.id.widget_title,list.get(music_index).gettitle());
 remoteviews.settextviewtext(r.id.widget_artist,list.get(music_index).getartist());
 remoteviews.settextcolor(r.id.widget_title,color.black);
 remoteviews.settextcolor(r.id.widget_artist,color.black);
 notify.contentview=remoteviews;
 manager.notify(100,notify);
}

④通知栏动作接收,使用的是内部类

public class buttonbroadcastreceiver extends broadcastreceiver{
 @override
 public void onreceive(context context, intent intent) {
  string action=intent.getaction();
  log.d(tag,"--------------------收到action:" action "--------------------------");
  if(action.equals(button_prev_id)){
   runonuithread(new runnable() {
    @override
    public void run() {
     ib_precious.performclick();
     return;
    }
   });
  }
  if(action.equals(button_play_id)){
   runonuithread(new runnable() {
    @override
    public void run() {
     ib_state.performclick();
     return;
    }
   });
  }
  if(action.equals(button_next_id)){
   runonuithread(new runnable() {
    @override
    public void run() {
     ib_next.performclick();
     return;
    }
   });
  }
  if(action.equals(button_close_id)){
   handler.removecallbacks(runnable);
   mybinder.closemusic();
   unbindservice(connection);
   if(remoteviews!=null){
    manager.cancel(100);
   }
   unregisterreceiver(buttonbroadcastreceiver);
   finish();
  }
 }
}

6.全屏显示

①在androidmanifest文件里面配置主题样式android:theme="@style/theme.appcompat.light.noactionbar">
然后在oncreate方法里在setcontentview(r.layout.activity_main);之前
设置:

if(build.version.sdk_int>=21){
 view decorview=getwindow().getdecorview();
 decorview.setsystemuivisibility(view.system_ui_flag_layout_fullscreen|view.system_ui_flag_layout_stable);
 getwindow().setstatusbarcolor(color.transparent);
}

7.设置歌曲选中后的样式

①在res目录下的drawable资源下新建一个类型为selector的xml文件,里面设置属性



 
 

②在adapter里面设置getview

currentitem == position){
 holder.tv_title.setselected(true);
 holder.tv_position.setselected(true);
 holder.tv_duration.setselected(true);
 holder.tv_artist.setselected(true);
}else{
 holder.tv_title.setselected(false);
 holder.tv_position.setselected(false);
 holder.tv_duration.setselected(false);
 holder.tv_artist.setselected(false);
}

注意:在使用的时候可能需要手动去设置里面打开权限

关于android10.0如何实现本地音乐播放?就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

网站地图