基于android的网络音乐播放器-播放控制界面(九)

来源:互联网 发布:java 自定义类加载器 编辑:程序博客网 时间:2024/05/29 02:19

到这里我们的音乐播放器已经有了播放,收藏,搜索网络音乐并下载(包括多线程断点下载)等基本功能,下面将开发播放界面——实现音乐播放的控制-上/下一首,播放/暂停,循环控制,专辑图片的加载,歌词的加载和解析并支持滑动改变进度。这些实现主要在一个类里-PlayMusicActivity.java

package com.sprd.easymusic;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.UnsupportedEncodingException;import java.lang.ref.SoftReference;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import java.net.URLEncoder;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import com.android.volley.RequestQueue;import com.android.volley.Response;import com.android.volley.VolleyError;import com.android.volley.Request.Method;import com.android.volley.toolbox.StringRequest;import com.android.volley.toolbox.Volley;import com.sprd.easymusic.fragment.NetFragment;import com.sprd.easymusic.myview.LrcView;import com.sprd.easymusic.service.MusicService;import com.sprd.easymusic.util.BitmapUtil;import com.sprd.easymusic.util.DownloadUtil;import com.sprd.easymusic.util.LrcLine;import com.sprd.easymusic.util.StringUtil;import android.app.ActionBar;import android.app.Activity;import android.app.Service;import android.content.BroadcastReceiver;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.content.ServiceConnection;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.util.Log;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.View.OnClickListener;import android.view.animation.Animation;import android.view.animation.AnimationUtils;import android.widget.ImageView;import android.widget.ProgressBar;import android.widget.SeekBar;import android.widget.SeekBar.OnSeekBarChangeListener;import android.widget.TextView;import android.widget.Toast;public class PlayMusicActivity extends Activity implements MusicService.Watcher {    private static final String TAG = "PlayMusicActivity";    /**     * 发送音乐播放控制的广播给MianActivity,音乐的控制统一交给MainActivity管理     * 下一首,暂停,上一首,播放,继续播放     */    private static final String ACTION_NEXT_SONG = "action.nextsong";    private static final String ACTION_PAUSE = "action.pause";    private static final String ACTION_PRE_SONG = "action.presong";    private static final String ACTION_PLAY_SONG = "action.playsong";    private static final String ACTION_CONTINUE_PLAYING_SONG = "action.continueplaying";    /**     * 接收广播,下载歌词完成,下载专辑图片完成,更新播放状态     */    private static final String ACTION_DOWNLOADLRC_SUCCESS = "action_downloadlrc_success";    private static final String ACTION_DOWNLOADPIC_SUCCESS = "action_downloadpic_success";    private static final String ACTION_UPDATE_PLAYSTATE = "action.update.playstate";    /**     * 上一首,播放/暂停,下一首,专辑图片, 循环     */    private ImageView pre, playAndPause, next, albumPic, cycleView;    /**     * 音乐标题,歌手,时长,已播放时长     */    private TextView title, artist, duration, playedTimeView;    //加载歌词    private TextView loadLrc;    //歌词View--自定义View    private LrcView lrcView;    //进度条    private SeekBar seekBar;    //表征当前播放状态    private boolean isPlaying;    //表征暂停键是否按下,若为true则下次点击播放为继续播放,和isPlaying不冲突    private boolean pause;    //音乐播放器后台服务    private MusicService musicService;    private Context mContext;    //当前播放音乐的标题和歌手和时长    private String currentMusicTitle, currentMusicArtist;    private int currentMusicDuration;    //播放界面专辑图片的动画-旋转    private Animation playAnimation;    //当前播放的进度    private int currentProgress;    private Handler myHandler;    private static final int UPDATE_PROGRESS = 1;    //添加网络请求队列    private RequestQueue mQueue;    //搜索歌词的API    public static final String lrcApi = "http://geci.me/api/lyric/";    //歌词下载的URL和网络响应    private String lrcUrl;    private String lrcResponse;    //缓存专辑图片,避免每次从本地或网络加载    private Map<String, SoftReference<Bitmap>> playImageCacheMap = new HashMap<String, SoftReference<Bitmap>>();    //当前歌词是否正在展示    private boolean showLrc = false;    //循环控制切换对应的图片资源    private int[] cycleViewResource;    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.play);        mContext = this;        //绑定服务并获得musicService的引用        bindToService();        cycleViewResource = new int[] {R.drawable.cycle_list, R.drawable.cycle_single, R.drawable.cycle_random};        pre = (ImageView) findViewById(R.id.pre);        pre.setOnClickListener(musicClickListener);        playAndPause = (ImageView) findViewById(R.id.playAndpause);        playAndPause.setOnClickListener(musicClickListener);        next = (ImageView) findViewById(R.id.next);        next.setOnClickListener(musicClickListener);        albumPic = (ImageView) findViewById(R.id.lrcpic);        title = (TextView) findViewById(R.id.title);        artist = (TextView) findViewById(R.id.artist);        duration = (TextView) findViewById(R.id.duration);        playedTimeView = (TextView) findViewById(R.id.playedtime);        lrcView = (LrcView) findViewById(R.id.lrcview);        loadLrc = (TextView) findViewById(R.id.loadlrc);        loadLrc.setOnClickListener(musicClickListener);        cycleView = (ImageView) findViewById(R.id.cycleview);        cycleView.setOnClickListener(musicClickListener);        mQueue = Volley.newRequestQueue(mContext);        playAnimation = AnimationUtils.loadAnimation(mContext, R.anim.rotate);        seekBar = (SeekBar) findViewById(R.id.musicProgress);        seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {                if (fromUser) {//手动滑动进度条时的监听                    changeProgressFromUser(progress);//通知后台musicService改变播放进度                    //歌词信息跳转进度                    lrcView.checkLrcTime(StringUtil.formatDuration(progress), progress - currentProgress);                }            }            public void onStartTrackingTouch(SeekBar seekBar) {            }            public void onStopTrackingTouch(SeekBar seekBar) {            }        });        myHandler = new Handler() {            public void handleMessage(Message msg) {                switch (msg.what) {                case UPDATE_PROGRESS://更新播放进度                    int progress = (Integer)msg.obj;                    seekBar.setProgress(progress);                    String playedTime = StringUtil.formatDuration(progress);                    playedTimeView.setText(playedTime);                    lrcView.checkLrcTime(playedTime, 1);                    Log.d(TAG, "update progress success!");                    break;                default:                    break;                }            }        };        IntentFilter filter = new IntentFilter();        filter.addAction(ACTION_DOWNLOADLRC_SUCCESS);        filter.addAction(ACTION_DOWNLOADPIC_SUCCESS);        filter.addAction(ACTION_UPDATE_PLAYSTATE);        mContext.registerReceiver(updateReceiver, filter);    }    // 绑定服务时的ServiceConnection参数        private ServiceConnection conn = new ServiceConnection() {            // 绑定成功后该方法回调,并获得服务端IBinder的引用            public void onServiceConnected(ComponentName name, IBinder service) {                // 通过获得的IBinder获取PlayMusicService的引用                musicService = ((MusicService.MusicBinder) service).getService();                musicService.addWatcher(PlayMusicActivity.this);                //Toast.makeText(mContext, "onServiceConnected:musicService", Toast.LENGTH_LONG).show();            }            @Override            public void onServiceDisconnected(ComponentName name) {                Log.d(TAG, "onServiceDisconnected:musicService");            }        };        // 绑定服务MusicService        private void bindToService() {            bindService(new Intent(mContext,                    com.sprd.easymusic.service.MusicService.class), conn,                    Service.BIND_AUTO_CREATE);        }    //后台播放服务改变播放进度    public void changeProgressFromUser(int progress) {        musicService.changePlayProgress(progress);    }    protected void onStart() {        updataPlayState();        super.onStart();    }    protected void onDestroy() {        musicService = null;        this.unbindService(conn);        this.unregisterReceiver(updateReceiver);        super.onDestroy();    }    //更新播放状态    private void updataPlayState() {        Log.d(TAG, "updataPlayState");        currentMusicTitle = MainActivity.currentMusicTitle;        currentMusicArtist = MainActivity.currentMusicArtist;        currentMusicDuration = (int) MainActivity.currentMusicDuration;        isPlaying = MainActivity.isPlaying;        pause = MainActivity.pause;        seekBar.setMax((int)currentMusicDuration);        title.setText(currentMusicTitle);        artist.setText(currentMusicArtist);        duration.setText(StringUtil.formatDuration(currentMusicDuration));        if (isPlaying) {            playAndPause.setImageResource(android.R.drawable.ic_media_pause);            albumPic.startAnimation(playAnimation);        } else {            playAndPause.setImageResource(android.R.drawable.ic_media_play);            albumPic.clearAnimation();        }        showAlbumPic();        showLrc();    }    //控制音乐的播放转交给MianActivity    private void changeMusicState(Intent intent) {        Log.d(TAG, "changeMusicState:action = " + intent.getAction());        this.sendBroadcast(intent);    }    private OnClickListener musicClickListener = new OnClickListener() {        public void onClick(View v) {            if (v == pre) {//上一首                isPlaying = true;                pause = false;                showLrc = false;                Intent intent = new Intent(ACTION_PRE_SONG);                changeMusicState(intent);            } else if (v == playAndPause) {//播放或者暂停                if (pause) {//如果点击过暂停,下一次点击播放应当是继续播放                    isPlaying = true;                    pause = false;                    Intent intent = new Intent(ACTION_CONTINUE_PLAYING_SONG);                    changeMusicState(intent);                    return;                }                if (isPlaying) {//当前播放状态点击暂停                    isPlaying = false;                    pause = true;                    Intent intent = new Intent(ACTION_PAUSE);                    changeMusicState(intent);                } else {//首次点击播放键,此时isPlaying和pause均为false                    isPlaying = true;                    Intent intent = new Intent(ACTION_PLAY_SONG);                    changeMusicState(intent);                }            } else if (v == next) {//下一首                isPlaying = true;                pause = false;                showLrc = false;                Intent intent = new Intent(ACTION_NEXT_SONG);                changeMusicState(intent);            } else if (v == loadLrc) {//加载歌词                showLrc = true;                String  localUrl = getLocalPath(0);                File file = new File( localUrl);                if (file.exists()) {                    showLrc();                    return;                }                searchLrc(currentMusicTitle, currentMusicArtist);            } else if (v == cycleView) {//切换循环模式                MainActivity.cycle += 1;                if (MainActivity.cycle > 3) MainActivity.cycle = 1;                cycleView.setImageResource(cycleViewResource[MainActivity.cycle-1]);            }        }    };    //搜索歌词    private void searchLrc(String title, String artist) {        String searchTitle = null;        String searchArtist = null;        Log.d(TAG, currentMusicTitle + currentMusicArtist);        try {            searchTitle = URLEncoder.encode(StringUtil.removeReg(currentMusicTitle, null), "UTF-8");            searchArtist = URLEncoder.encode(StringUtil.removeReg(currentMusicArtist, null), "UTF-8");            Log.d(TAG, searchTitle + searchArtist);        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        }        String url = lrcApi + searchTitle + "/" + searchArtist;        if (searchTitle.indexOf("+")>=0 || searchArtist.indexOf("+")>=0) {            url = url.replace('+', ' ');        }        Log.d(TAG, "url = " + url );        StringRequest stringRequest = new StringRequest(Method.GET,url,                  new Response.Listener<String>() {                      @Override                      public void onResponse(String response) {                        Log.d(TAG, "response = " + response);                         //搜索歌词得到的响应                        lrcResponse = response;                                  analysisLrcUrl();                    }                  },                 new Response.ErrorListener() {                      @Override                      public void onErrorResponse(VolleyError error) {                        Log.e(TAG, error.getMessage(), error);                          Toast.makeText(PlayMusicActivity.this, "加载失败!", 300).show();                    }                  });        mQueue.add(stringRequest);      }    //搜索专辑图片    private void searchAlbumPic(String title, String artist) {        Toast.makeText(mContext, "搜索专辑图片...", 300).show();        //有些歌名和歌手信息中可能会携带些非原始的特殊符号需要去掉,比如《》.,之类的        String ti = StringUtil.removeReg(title, null);        String ar = StringUtil.removeReg(artist, null);        Log.d(TAG, "searchAlbumPic:ti = " + ti + " ar = " + ar);         //启动查询歌词的异步任务        new NetFragment().new SearchMusicTask(ti, ar, mContext).execute(NetFragment.getRealUrl(title));    }    //从响应中分离出歌词下载的URL    private void analysisLrcUrl() {        try {            JSONObject jo = new JSONObject(lrcResponse);            JSONArray result = jo.getJSONArray("result");            JSONObject firstResult = result.getJSONObject(0);            String lrc = firstResult.getString("lrc");            lrcUrl = lrc;            downloadLrc(lrcUrl);            Log.d(TAG, "lrc = " + lrc);        } catch (JSONException e) {            e.printStackTrace();            Toast.makeText(PlayMusicActivity.this, "抱歉,未搜索到歌词!", 300).show();        }    }    //下载歌词    private void downloadLrc(String lrcUrl) {        final String musicLrc = lrcUrl;        Toast.makeText(mContext, "正在下载歌词,请稍候...", 300).show();        String  localFile = getLocalPath(0);        File file = new File(localFile);        //开启一个线程下载歌词        DownloadUtil lrcUtil = new DownloadUtil(musicLrc, localFile, mContext, 1);        musicService.downloadMusic(lrcUtil);    }    //下载专辑图片    public void downloadAlbumPic(String albumPicUrl) {        if (albumPicUrl == null) {            Toast.makeText(mContext, "未搜索到匹配图片", 300).show();            albumPic.setImageResource(R.drawable.rotate);            return;        }        final String musicAlbumPic = albumPicUrl;        //Toast.makeText(mContext, "正在下载专辑图片,请稍候...", 300).show();        String localFile = getLocalPath(1);        File file = new File(localFile);        //开启两个线程下载专辑图片        DownloadUtil albumPicUtil = new DownloadUtil(musicAlbumPic, localFile, mContext, 2);        musicService.downloadMusic(albumPicUtil);    }    //显示歌词    protected void showLrc() {        if (!showLrc) {            lrcView.setLrcList(null);            lrcView.setCurrentLrcIndex(0);            lrcView.setVisibility(View.GONE);            loadLrc.setVisibility(View.VISIBLE);            return;        }        String  localFile = getLocalPath(0);        File file = new File(localFile);        if (!file.exists()) return;        //歌词的解析只需传进去一个歌词文件路径,自定义view-lrcView内部已经封装好        lrcView.loadLrc(localFile);        loadLrc.setVisibility(View.INVISIBLE);    }    //显示专辑图片    private void showAlbumPic() {        //首先从缓存中查询是否已加载有该专辑图片        if (playImageCacheMap != null) {            Log.d(TAG, "playImageCacheMap.size = " + playImageCacheMap.size());            SoftReference<Bitmap> sBitmap =                     playImageCacheMap.get(currentMusicTitle+currentMusicArtist);            if (sBitmap != null) {                Bitmap bitmap = sBitmap.get();                if (bitmap != null) {                    albumPic.setImageBitmap(bitmap);                    Log.d(TAG, "get image from ImageCacheMap");                    return;                } else {                    playImageCacheMap.remove(currentMusicTitle+currentMusicArtist);                }            }        }        //如果缓存中还未存入该专辑图片或者已经被回收,从本地加载并添加到缓存        final String  localFile = getLocalPath(1);        File file = new File(localFile);        if (file.exists()) {            myHandler.post(new Runnable() {                public void run() {                    Bitmap bitmap = BitmapFactory.decodeFile(localFile);                    //int size = Math.min(lrcPic.getWidth(), lrcPic.getHeight());                    //Bitmap bitmap = BitmapUtil.getScropBitmap(localFile, size, size);                    playImageCacheMap.put(currentMusicTitle + currentMusicArtist, new SoftReference<Bitmap>(bitmap));                    Log.d(TAG, "add bitmap to ImageCacheMap, bytes = " + bitmap.getByteCount());                    albumPic.setImageBitmap(bitmap);                }            });            return;        }        //如果缓存和本地均没有专辑图片则从网络中搜索并加载        searchAlbumPic(currentMusicTitle, currentMusicArtist);    }    //获取本地文件路径,type为0表示歌词路径,为1表示专辑图片路径    private String getLocalPath(int type) {        String result = null;        MainActivity.createFileDir();        if (type == 0) {            result = MainActivity.downloadedPath                    + "/lrc/"+ currentMusicTitle + "-" + currentMusicArtist + ".lrc";        } else {            result = MainActivity.downloadedPath             + "/album/"+ currentMusicTitle + "-" + currentMusicArtist + ".jpg";        }        return result;    }    @Override    public void update(int currentProgress) {        this.currentProgress = currentProgress;        myHandler.sendMessage(myHandler.obtainMessage(UPDATE_PROGRESS, currentProgress));    }    private BroadcastReceiver updateReceiver = new BroadcastReceiver() {        public void onReceive(Context context, Intent intent) {            if (intent.getAction().equals(ACTION_DOWNLOADLRC_SUCCESS)) {                Toast.makeText(mContext, "下载歌词成功!", 100).show();                showLrc();            } else if (intent.getAction().equals(ACTION_DOWNLOADPIC_SUCCESS)) {                Toast.makeText(mContext, "下载专辑图片成功!", 100).show();                showAlbumPic();            } else if (intent.getAction().equals(ACTION_UPDATE_PLAYSTATE)) {                boolean autoChange = intent.getBooleanExtra("autoChange", false);                if (autoChange) showLrc = !autoChange;                updataPlayState();            }        }    };}

其中StringUtil是字符串处理工具类:

package com.sprd.easymusic.util;import android.media.MediaPlayer;public class StringUtil {    // 将音乐时长-毫秒转换为00:00格式    public static String formatDuration(long dur) {        long totalSecond = dur / 1000;        String minute = totalSecond / 60 + "";        if (minute.length() < 2)            minute = "0" + minute;        String second = totalSecond % 60 + "";        if (second.length() < 2)            second = "0" + second;        return minute + ":" + second;    }    public static String removeReg(String source, String reg) {        if (reg!= null) {            return source.replaceAll(reg, "");        }        //保留中文和英文字符        return source.replaceAll("[^a-zA-Z \u4e00-\u9fa5]", "");    }    public static String getMusicDuration(String url) {        int duration = 0;        MediaPlayer mp = null;        try {            mp = new MediaPlayer();            mp.reset();            mp.setDataSource(url);            mp.prepare();            duration = mp.getDuration();        } catch (Exception e) {            e.printStackTrace();        } finally {            mp.release();        }        return formatDuration(duration);    }    public static long getMusicLongDuration(String url) {        long duration = 0;        MediaPlayer mp = null;        try {            mp = new MediaPlayer();            mp.reset();            mp.setDataSource(url);            mp.prepare();            duration = mp.getDuration();        } catch (Exception e) {            e.printStackTrace();        } finally {            mp.release();        }        return duration;    }}

搜索专辑图片的异步任务与网络音乐搜索的异步任务是同一个,在前面的章节中可以看到搜索歌曲时有保存专辑图片,只是没有拿来使用。

PlayMusicActivity.java的布局很简单,如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/playlayout"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@drawable/myshape"    android:gravity="top"    android:orientation="vertical"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin" >    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_weight="2"        android:gravity="center"        android:orientation="vertical" >        <TextView            android:id="@+id/title"            android:layout_width="wrap_content"            android:layout_height="30dp"            android:textAppearance="?android:attr/textAppearanceLarge"            android:textColor="#ff0000" />        <TextView            android:id="@+id/artist"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textAppearance="?android:attr/textAppearanceMedium"            android:textColor="#00ff00" />    </LinearLayout>    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="20" >        <!--<ImageView            android:id="@+id/lrcpic"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:layout_marginTop="20dp"            android:src="@drawable/rotate" />  -->        <com.sprd.easymusic.myview.CircleImageView             android:id="@+id/lrcpic"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:layout_marginTop="20dp"            android:alpha="0.6"            android:src="@drawable/rotate"/>        <com.sprd.easymusic.myview.LrcView            android:id="@+id/lrcview"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:layout_marginTop="20dp"            android:alpha="1.0"            android:visibility="gone"/>    </RelativeLayout>    <TextView        android:id="@+id/loadlrc"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_gravity="center"        android:layout_marginBottom="10dp"        android:layout_weight="4"        android:gravity="center"        android:text="查看歌词"        android:textAppearance="?android:attr/textAppearanceMedium"        android:textColor="#ff00ff" />    <SeekBar        android:id="@+id/musicProgress"        android:layout_width="match_parent"        android:layout_height="30dp"        android:layout_marginBottom="10dp" />    <LinearLayout        android:id="@+id/seekbar"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginBottom="10dp"        android:orientation="horizontal" >        <TextView            android:id="@+id/playedtime"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginLeft="5dp"            android:layout_weight="4"            android:text="00:00"            android:textColor="#ffffff" />        <TextView            android:id="@+id/duration"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_weight="4"            android:gravity="right"            android:textColor="#ffffff" />    </LinearLayout>    <LinearLayout        android:id="@+id/controlarea"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginBottom="30dp"        android:layout_weight="5"        android:gravity="center"        android:orientation="horizontal" >        <ImageView            android:id="@+id/pre"            android:layout_width="30dp"            android:layout_height="30dp"            android:layout_marginLeft="30dp"            android:src="@drawable/pre" />        <ImageView            android:id="@+id/playAndpause"            android:layout_width="30dp"            android:layout_height="30dp"            android:layout_marginLeft="50dp"            android:background="#ffffff"            android:src="@drawable/play" />        <ImageView            android:id="@+id/next"            android:layout_width="30dp"            android:layout_height="30dp"            android:layout_marginLeft="50dp"            android:src="@drawable/next" />        <ImageView            android:id="@+id/cycleview"            android:layout_width="30dp"            android:layout_height="30dp"            android:layout_marginLeft="50dp"            android:src="@drawable/cycle_list" />    </LinearLayout></LinearLayout>

这是一个线性布局,从各个view的id就可以看出其作用了,这里也不多做解释了。
大概界面预览图如下(专辑图片eclipse显示有误):
PlayMusicActivity界面

音乐播放器已完成,下载地址:
Android音乐播放器

0 0
原创粉丝点击