播放音乐列表实现----利用服务Service和广播Broadcast

这篇博客详细介绍了如何实现音乐播放功能,包括创建服务类播放音乐、注册服务、构建ListView、设置点击事件、利用Broadcast传递音乐播放状态以及处理SeekBar拖动事件,通过实例代码展示了整个过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


本篇实现音乐播放的功能,利用拖动条,listview以及广播返回消息给活动,更新UI界面。

效果演示:

这里写图片描述

这里无法听到音乐,这里只关注界面效果吧。

代码分析:

1.创建服务类,播放相应路径下的音乐:

这里写图片描述

这里音乐的路径是由主活动中的intent中存放的,开启服务时直接传过来即可。

2.注册服务:

这里写图片描述

这里由于服务类的包和活动的包不是同一个包,所以需要写全称。

3.创建Listview:

创建Listview的适配器:
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
public class MusicAdapter extends BaseAdapter {
    private LayoutInflater inflater;
    private File[] musics;
    private MediaMetadataRetriever mmr ;

    public MusicAdapter(LayoutInflater inflater, File[] musics) {
        this.inflater = inflater;
        this.musics = musics;
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return musics.length;
    }

    @Override
    public Object getItem(int arg0) {
        // TODO Auto-generated method stub
        return arg0;
    }

    @Override
    public long getItemId(int arg0) {
        // TODO Auto-generated method stub
        return arg0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup arg2) {
        // TODO Auto-generated method stub
        ViewHolder vh = null;
        if (convertView == null) {
            vh = new ViewHolder();
            convertView = inflater.inflate(R.layout.music_item_list, null);
            vh.textviewName = (TextView) convertView.findViewById(R.id.textview_music_name);
            vh.imgPic = (ImageView) convertView.findViewById(R.id.img_pic);
            vh.textviewArtist = (TextView) convertView.findViewById(R.id.textview_artist);
            convertView.setTag(vh);

        } else {
            vh = (ViewHolder) convertView.getTag();
        }
        // 放资源
        vh.textviewName.setText(musics[position].getName());
        // 放音乐图片和艺术家
        mmr = new MediaMetadataRetriever();
        mmr.setDataSource(musics[position].getAbsolutePath());

        String author = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);

        if (author != null) {
            vh.textviewArtist.setText("艺术家:" + author);
        } else {
            vh.textviewArtist.setText("艺术家:未知");
        }
        // 放图片
        byte[] imag = mmr.getEmbeddedPicture();// 得到图片
        if (imag != null) {
            Bitmap bitmap = BitmapFactory.decodeByteArray(imag, 0, imag.length);
            vh.imgPic.setImageBitmap(bitmap);
        } else {
            vh.imgPic.setImageResource(R.drawable.den);
        }
        return convertView;
    }

    class ViewHolder {
        ImageView imgPic;
        TextView textviewName;
        TextView textviewArtist;
    }

}


Listview适配器这里不做详解,但需要注意,获取music中自带的图片和艺术家等自带信息的方法:
要通过MediaMetadataRetriever类:获取其实例,然后设置Source,现在的MediaMetadataRetriever就可以去获得这个指定路径音乐的信息。

MediaMetadataRetriever mmr = new MediaMetadataRetriever();
        mmr.setDataSource(musics[position].getAbsolutePath());


这里仅介绍了获取图片和艺术家的方法:
获取艺术家名称:mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);

获取图片:

mmr.getEmbeddedPicture();//获得的是图片的字节流
Bitmap bitmap = BitmapFactory.decodeByteArray(imag, 0, imag.length);//获得Bitmap形式的图片


即:
这里写图片描述

创建音乐列表,并加到Listview中:

这里写图片描述
添加到适配器
这里写图片描述

4.为Listview创建点击事件:

这里写图片描述

在listview的点击事件中开启服务,并携带音乐列表相应音乐的路径。
这里同时携带的还有一个type类型,用于服务判断,收到的指示是开始播放音乐,还是跳转音乐到相应位置,添加这个type是因为后面需要拖动seekbar来改变播放位置。

5.服务播放音乐后,将信息传给活动,改变seekBar位置,用广播实现:

活动里创建广播子类接收消息,接收seekbar的最大值及进程位置:

这里写图片描述

接收到广播,通过type(注:这是另一个type,和之前的不同)来判断接收的是音乐的总时长,还是当前的进度。

动态注册广播:

这里写图片描述

服务子类发送广播:

这里写图片描述

这里写图片描述

由于需要每隔1s通知活动,UI主线程绘制SeekBar,所以发送的音乐播放位置需要在新建的子线程中,【子线程每运行1s,都发送广播,注意主线程接收广播也是已消息队列的形式。】
这里还发送了播放中音乐的总时长,这个发送不用在子线程中进行,因为它不改变,所以只需要在播放音乐后直接发送时长,并启动发送进程位置的子线程就OK。

6.seekBar拖动实现更改音乐播放位置:

这里写图片描述

对SeekBar添加监听事件,监听位置的改变,注:将代码写到onStopTrackingTouch(SeekBar seekbar)方法中,它在鼠标停止拖动sekbar时被调用。不要放在onProgressChanged方法中,这是只要进度条位置改变,它就调用,而我们在前面是服务中有子线程在返回数据控制进度,进度一定一直在改变,所以onProgressChanged方法会一直调用,结果就会造成一播放就会有两个一前一后的叠音出现。

所有代码:

主活动:
public class MainActivity extends Activity implements OnClickListener {
    private ListView mListview;
    private SeekBar mBar;
    private Button mButtonPlay;
    private MusicAdapter musicAdapter;
    private File[] musics;
    private MusicBroadcast musicbroad;
    private TextView textviewDuration;
    private TextView textviewCurrent; 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListview = (ListView) findViewById(R.id.listview_music);
        mBar = (SeekBar) findViewById(R.id.progressBar);
        mButtonPlay = (Button) findViewById(R.id.button_play);
        textviewDuration=(TextView) findViewById(R.id.textview_duration);
        textviewCurrent = (TextView) findViewById(R.id.textview_current);
        mButtonPlay.setOnClickListener(this);
        //注册广播
        musicbroad = new MusicBroadcast();
        IntentFilter filter = new IntentFilter("music_broadcast_code_action");
        registerReceiver(musicbroad, filter);



        getMusicList();
        musicAdapter = new MusicAdapter(getLayoutInflater(), musics);
        mListview.setAdapter(musicAdapter);
        mListview.setOnItemClickListener(new OnItemClickListener() {// 对list添加点击事件,点击播放音乐

                    @Override
                    public void onItemClick(AdapterView<?> arg0, View arg1,
                            int positon, long arg3) {
                        // TODO Auto-generated method stub//启动服务播放对应音乐
                        Intent intent = new Intent(getApplicationContext(),
                                MusicService.class);
                        intent.putExtra("music_path",
                                musics[positon].getAbsolutePath());// 得到点击音乐的路径
                        intent.putExtra("type", Config.TYPE_START_MUSIC);
                        startService(intent);// 启动服务//不要忘了注册,注册,注册
                        Log.d("message",
                                "启动:" + musics[positon].getAbsolutePath());

                    }
                });


        //为seekbar建立了一个监听事件;
        mBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

            @Override
            public void onStopTrackingTouch(SeekBar seekbar) {//当停止拖动后,调用此方法
                // TODO Auto-generated method stub
                Intent intent = new Intent(getApplicationContext(),MusicService.class);
                intent.putExtra("type", Config.TYPE_CHANG_MUSIC);
                intent.putExtra("progress_to", seekbar.getProgress());//得到拖动seekbar的位置
                startService(intent);//启动服务

            }

            @Override
            public void onStartTrackingTouch(SeekBar arg0) {//开始拖动时调用
                // TODO Auto-generated method stub

            }

            @Override
            public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {//进度改变时就调用
                // TODO Auto-generated method stub

            }
        });

    }

    public void getMusicList() {
        // 获取音乐列表
        File sdcard = Environment.getExternalStorageDirectory();// 获得手机sd卡路径
        Log.d("music", sdcard + "");
        File musicDir = new File(sdcard, "/Music");// 获得存放音乐的目录
        musics = musicDir.listFiles();// 获得目录下的所有文件

    }

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch (v.getId()) {
        case R.id.button_play:

            break;

        default:
            break;
        }

    }

    // 建立广播接收音乐服务发送的广播信息
    class MusicBroadcast extends BroadcastReceiver {
        /**seekbar:必须设置总长度,和setprogress中间进程,才能后正常走动。
         * 收到的时间为ms单位:设置成分:秒* */
        @Override
        public void onReceive(Context context, Intent intent) {
            // TODO Auto-generated method stub
            int type = intent.getIntExtra("type", 0);
            switch (type) {
            case MusicService.TYPE_MUSIC_DURATION://获得的是总时长
                int duration = intent.getIntExtra("duration", 0);
                int duration_miao = duration/1000;
                int duration_minite =duration_miao/60;
                Log.d("message_duration", duration+"");
                textviewDuration.setText(duration_minite+"分 : "+duration_miao%60+"秒");
                mBar.setMax(duration);
                break;
            case MusicService.TYPE_MUSIC_CURRENT://获得的是当前播放到的时间位置
                int current = intent.getIntExtra("current", 0);
                Log.d("message_current", current+"");
                int current_miao = current/1000;
                int current_minite =current_miao/60;
                textviewCurrent.setText(current_minite+"分 : "+current_miao%60+"秒");
                mBar.setProgress(current);
                break;
            default:
                break;
            }

        }

    }

}
主活动的布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" 
    android:orientation="vertical">

<ListView 
    android:id="@+id/listview_music"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    ></ListView>
<LinearLayout 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >
    <TextView 
        android:id="@+id/textview_duration"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="left"
        android:text="总时长"/>
     <TextView 
        android:id="@+id/textview_current"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="right"
        android:layout_marginLeft="30dp"
        android:text="当前时间"/>
</LinearLayout>
  <SeekBar
      android:id="@+id/progressBar" 
     android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
<Button 
    android:id="@+id/button_play"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="播放音乐"/>

</LinearLayout>
服务类:
public class MusicService extends Service {
    public static final int TYPE_MUSIC_DURATION = 0;// 类型是总时长
    public static final int TYPE_MUSIC_CURRENT = 1;// 类型是总时长
    private MediaPlayer player;

    @Override
    public IBinder onBind(Intent arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
        int type = intent.getIntExtra("type", 0);
        switch (type) {
        case Config.TYPE_CHANG_MUSIC:
            int progress = intent.getIntExtra("progress_to", 0);
            player.seekTo(progress);

            break;
        case Config.TYPE_START_MUSIC:
            startMusic(intent);

            break;

        default:
            break;
        }


        return super.onStartCommand(intent, flags, startId);
    }

    // 播放新音乐
    public void startMusic(Intent intent) {
        String path = intent.getStringExtra("music_path");// 1.获得要播放的音乐路径
        Log.d("music_path", path);
        if (player == null) {
            player = new MediaPlayer();// 2.避免每次点击建立新对象,会出现叠放音乐的错误
        }
        player.reset();
        try {
            player.setDataSource(path);// 设置资源
            player.prepare();
            player.setOnPreparedListener(new OnPreparedListener() {

                @Override
                public void onPrepared(MediaPlayer arg0) {
                    // TODO Auto-generated method stub
                    player.start();

                    // 获得音乐总时长,通过广播返回活动中
                    int duration = player.getDuration();
                    Log.d("Duration_service", duration+"");
                    Intent intent = new Intent("music_broadcast_code_action");
                    intent.putExtra("type", TYPE_MUSIC_DURATION);
                    intent.putExtra("duration", duration);

                    sendBroadcast(intent);// 不要忘了动态注册广播
                    Thread thread = new MyThread();
                    thread.start();

                }
            });
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalStateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    // 创建一个线程,返回当前时间
    class MyThread extends Thread {// 在子线程中返回当前时间,需要每隔1秒发送//一定要写在循环里
        @Override
        public void run() {
            // TODO Auto-generated method stub
            super.run();
            while (player.isPlaying()) {
                int current = player.getCurrentPosition();
                Intent intent = new Intent("music_broadcast_code_action");
                intent.putExtra("type", TYPE_MUSIC_CURRENT);
                intent.putExtra("current", current);
                sendBroadcast(intent);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        }
    }

}
ListView适配器:
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
public class MusicAdapter extends BaseAdapter {
    private LayoutInflater inflater;
    private File[] musics;
    private MediaMetadataRetriever mmr ;

    public MusicAdapter(LayoutInflater inflater, File[] musics) {
        this.inflater = inflater;
        this.musics = musics;
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return musics.length;
    }

    @Override
    public Object getItem(int arg0) {
        // TODO Auto-generated method stub
        return arg0;
    }

    @Override
    public long getItemId(int arg0) {
        // TODO Auto-generated method stub
        return arg0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup arg2) {
        // TODO Auto-generated method stub
        ViewHolder vh = null;
        if (convertView == null) {
            vh = new ViewHolder();
            convertView = inflater.inflate(R.layout.music_item_list, null);
            vh.textviewName = (TextView) convertView.findViewById(R.id.textview_music_name);
            vh.imgPic = (ImageView) convertView.findViewById(R.id.img_pic);
            vh.textviewArtist = (TextView) convertView.findViewById(R.id.textview_artist);
            convertView.setTag(vh);

        } else {
            vh = (ViewHolder) convertView.getTag();
        }
        // 放资源
        vh.textviewName.setText(musics[position].getName());
        // 放音乐图片和艺术家
        mmr = new MediaMetadataRetriever();
        mmr.setDataSource(musics[position].getAbsolutePath());

        String author = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);

        if (author != null) {
            vh.textviewArtist.setText("艺术家:" + author);
        } else {
            vh.textviewArtist.setText("艺术家:未知");
        }
        // 放图片
        byte[] imag = mmr.getEmbeddedPicture();// 得到图片
        if (imag != null) {
            Bitmap bitmap = BitmapFactory.decodeByteArray(imag, 0, imag.length);
            vh.imgPic.setImageBitmap(bitmap);
        } else {
            vh.imgPic.setImageResource(R.drawable.den);
        }
        return convertView;
    }

    class ViewHolder {
        ImageView imgPic;
        TextView textviewName;
        TextView textviewArtist;
    }

}
listview的item布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/img_pic"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_vertical|center_horizontal"
        android:src="@drawable/ic_launcher" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/textview_music_name"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical|center_horizontal"
            android:text="音乐名"
            android:layout_margin="10dp" />

        <TextView
            android:id="@+id/textview_artist"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical|center_horizontal"
            android:text="艺术家" 
            android:layout_marginBottom="10dp"
            />
    </LinearLayout>

</LinearLayout>

改进:

在效果图中我们看到只有第一个显示出了图片,但其实在手机中是正常显示的,
后期,我增加了功能,加上了暂停,前一首,后一首的功能,还不错呦,只是需要注意暂停后,会使之前控制刷新seekbar的子线程关掉,这里我们需要在建立一个子线程去刷新seekbar。
这里仅进行效果演示:
这里写图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值