基于Android Studio开发的音乐播放器

Lily1203

发布日期: 2020-11-10 11:00:49 浏览量: 185
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

一、课题内容和要求

  • 内容:设计简单的自定义音乐播放器,并实现其基本功能

  • 要求

    • 正常播放常见音乐格式,如MP3、AMR等
    • 可暂停并利用进度条实现快进和倒退功能
    • 显示歌曲信息
    • 扫描指定目录下歌曲信息,并加入播放列表
    • 实现歌曲播放列表相关功能即上一首、下一首等,以及单曲循环、列表循环、顺序循环、随机播放等功能
    • 可以在安卓系统上面运行并演示

二、课题需求分析

本课题目标音乐播放器的功能框架图如图1所示。

  • 通过音乐文件指定路径将其扫描添加到音乐播放列表内,并读取其相关信息到指定变量当中,如:编号、歌曲名、音乐家、专辑、长度等

  • 支持常见格式的音乐文件播放和信息显示

  • 通过广播接收器以及相关响应函数对音乐播放器各种功能如快进、暂停、上下首歌切换,进度条调整等等

  • 支持音乐播放模式的切换如单曲循环等等

  • 支持界面调整如主题切换等等

三、程序设计

3.1 主要数据结构

先定义歌曲列表项类的自身所需的信息。

  1. public class Music {
  2. private int id; //编号
  3. private String name = null; //歌曲名
  4. private String artist = null; //艺术家
  5. private String path = null; //路径
  6. private String duration = null; //长度
  7. private String album = null; //专辑名
  8. }

进入主界面所包含的数据及控件有如下所示。

  1. public class MainActivity extends AppCompatActivity {
  2. private boolean firstPlay = true;
  3. private ImageButton imgBtn_Previous;
  4. private ImageButton imgBtn_PlayOrPause;
  5. private ImageButton imgBtn_Stop;
  6. private ImageButton imgBtn_Next;
  7. private ListView list;
  8. private RelativeLayout root_Layout;
  9. private SeekBar seekBar;
  10. private Handler seekBarHandler;
  11. private TextView text_Current;
  12. private TextView text_Duration;
  13. private TextView textView;
  14. private ArrayList<Music> mainMusicList;
  15. //歌曲序号,下标从0开始
  16. private int number = 0;
  17. //播放状态
  18. private int status;
  19. //绑定广播接收器,可以接收广播
  20. private StatusChangedReceiver receiver;
  21. //歌曲的持续时间和当前位置,用作进度条
  22. private int duration;
  23. private int time;

广播服务所拥有的数据和组件、以及播放组件所拥有的各种状态如下所示。

  1. public class MusicService extends Service {
  2. //播放控制命令,标识操作
  3. public static final int COMMAND_UNKNOWN = -1;
  4. public static final int COMMAND_PLAY = 0;
  5. public static final int COMMAND_PAUSE = 1;
  6. public static final int COMMAND_STOP = 2;
  7. public static final int COMMAND_RESUME = 3;
  8. public static final int COMMAND_PREVIOUS = 4;
  9. public static final int COMMAND_NEXT = 5;
  10. public static final int COMMAND_CHECK_IS_PLAYING = 6;
  11. public static final int COMMAND_SEEK_TO = 7;
  12. public static final int COMMAND_RANDOM = 8;
  13. public static final int COMMAND_LIST_CYCLE_UP = 9;
  14. public static final int COMMAND_LIST_CYCLE_DOWN = 10;
  15. //播放状态
  16. public static final int STATUS_PLAYING = 0;
  17. public static final int STATUS_PAUSED = 1;
  18. public static final int STATUS_STOPPED = 2;
  19. public static final int STATUS_COMPLETED = 3;
  20. //广播标识
  21. public static final String BROADCAST_MUSICSERVICE_CONTROL = "MusicService.ACTION_CONTROL";
  22. public static final String BROADCAST_MUSICSERVICE_UPDATE_STATUS = "MusicService.ACTION_UPDATE";
  23. //歌曲序号
  24. private int number = 0;
  25. private int status;
  26. //媒体播放类
  27. private MediaPlayer player = new MediaPlayer();
  28. //广播接收器
  29. private CommandReceiver receiver;
  30. //电话标识
  31. private boolean phone = false;

3.2 主要流程

主要流程如图2所示。

音乐列表的展示:在扫描本地音乐信息后就渲染音乐列表,使用的是ListView组件进行展示。如图2所示。

睡眠模式的实现:在菜单选项卡中选择睡眠模式后可进行睡眠模式的设置,然后进入睡眠状态。如图3所示。

调整音量:在主界面右上角显示当前音量,并且可以通过直接拖动进度条或是按手机音量功能键来调整媒体音量。如图4所示。

与第一个同学的初始化列表功能函数相结合,初始化列表同时显示音乐文件的信息。流程图如图1所示。

暂停、上一首、下一首的功能实现,利用广播信号接收器实现动作与响应的联系,从而对播放的歌曲进行相关的操作,流程图如图2所示。

单曲循环、列表循环、随机播放、顺序播放与主菜单相联系,通过广播接收器和接收广播命令的内部类和相关的响应函数实现各种播放模式下的音乐播放,流程图如图3所示。

打开音乐播放器会进入到主界面。如图3展示。

音乐播放器进入主界面后,会自动扫描手机本地上的所有音乐,并添加到播放列表中,可以点击选择其中一首歌曲进行播放。

主界面左上方包含主标题,右上方包含菜单功能选项。菜单功能下方包含音乐音量设置。然后包含音乐列表,使用的是ListView组件进行展示。最下方包含音乐播放上一首、播放、暂停、播放下一首的控件,它的下方是音乐播放进度条以及音乐时长和目前所播放的时长。

点击右上方的菜单按钮会弹出菜单选项卡。如图4展示。

菜单选项卡包含。主题切换,显示歌词,播放模式,睡眠模式,关于,退出等功能。

点击菜单中的主题功能会弹出选择主题框。如图5。

只要点击选择项,就可以切换到相应的主题。

点击右上角菜单中的播放模式选项,就会弹出播放模式。如图6所示。

播放模式包含如各大音乐播放器所包含的功能。有顺序播放、单曲循环、列表循环、随机播放这四种。顺序播放模式可以按照已经播放的列表播放,单曲循环模式是当前歌曲一直循环播放,列表循环模式是已播放列表都播放完成后再回头播放第一首歌曲这样循环,随机播放是播放完一首后会随机选择列表中的其中一首进行播放。

点击右上方菜单中的关于说明按钮,会弹出关于本app相关的信息说明框。如下图所示。

点击右上方菜单中的退出按钮,可以直接退出该APP程序,音乐也自然停止播放。

接下要说明的界面是播放功能界面,该界面有用操作歌曲播放、暂停、停止等功能,也是最核心的功能。如图7所示。

四、关键源码

  1. import android.app.AlertDialog;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.DialogInterface;
  5. import android.content.Intent;
  6. import android.content.IntentFilter;
  7. import android.database.Cursor;
  8. import android.media.AudioManager;
  9. import android.os.Bundle;
  10. import android.os.Handler;
  11. import android.os.Message;
  12. import android.provider.MediaStore;
  13. import android.support.v7.app.AppCompatActivity;
  14. import android.view.KeyEvent;
  15. import android.view.Menu;
  16. import android.view.MenuItem;
  17. import android.view.View;
  18. import android.widget.AdapterView;
  19. import android.widget.CompoundButton;
  20. import android.widget.ImageButton;
  21. import android.widget.ImageView;
  22. import android.widget.ListView;
  23. import android.widget.RelativeLayout;
  24. import android.widget.SeekBar;
  25. import android.widget.SimpleAdapter;
  26. import android.widget.Switch;
  27. import android.widget.TextView;
  28. import android.widget.Toast;
  29. import java.util.ArrayList;
  30. import java.util.HashMap;
  31. import java.util.List;
  32. import java.util.Map;
  33. import java.util.Timer;
  34. import java.util.TimerTask;
  35. public class Music {
  36. private int id; //编号
  37. private String name = null; //歌曲名
  38. private String artist = null; //艺术家
  39. private String path = null; //路径
  40. private String duration = null; //长度
  41. private String album = null; //专辑名
  42. public Music(int id, String name, String artist, String path, String duration, String album) {
  43. this.id = id;
  44. this.name = name;
  45. this.artist = artist;
  46. this.path = path;
  47. this.duration = duration;
  48. this.album = album;
  49. }
  50. public int getId() {
  51. return id;
  52. }
  53. public String getName() {
  54. return this.name;
  55. }
  56. public String getArtist() {
  57. return this.artist;
  58. }
  59. public String getPath() {
  60. return this.path;
  61. }
  62. public String getDuration() {
  63. return this.duration;
  64. }
  65. public String getAlbum() {
  66. return album;
  67. }
  68. @Override
  69. public String toString() {
  70. return name + " " + artist + " " + album;
  71. }
  72. }
  73. import java.util.ArrayList;
  74. public class MusicList {
  75. private static ArrayList<Music> musicList = new ArrayList<>();
  76. public MusicList() {
  77. }
  78. public static ArrayList<Music> getMusicList() {
  79. return musicList;
  80. }
  81. }
  82. //绑定广播接收器,可以接收广播
  83. bindStatusChangedReceiver();
  84. initSeekBarHandler();
  85. startService(new Intent(this, MusicService.class));
  86. status = MusicService.COMMAND_STOP;
  87. //初始化音乐列表
  88. private void initMusicList() {
  89. //避免重复添加音乐
  90. if (mainMusicList.isEmpty()) {
  91. Cursor mMusicCursor = this.getContentResolver().query(
  92. MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
  93. new String[]{
  94. MediaStore.Audio.Media.TITLE,
  95. MediaStore.Audio.Media.DURATION,
  96. MediaStore.Audio.Media.ALBUM,
  97. MediaStore.Audio.Media.ARTIST,
  98. MediaStore.Audio.Media._ID,
  99. MediaStore.Audio.Media.DATA,
  100. MediaStore.Audio.Media.DISPLAY_NAME
  101. }, null, null,
  102. MediaStore.Audio.AudioColumns.TITLE
  103. );
  104. if (mMusicCursor != null) {
  105. int indexTitle = mMusicCursor.getColumnIndex(MediaStore.Audio.AudioColumns.TITLE);
  106. int indexArtist = mMusicCursor.getColumnIndex(MediaStore.Audio.AudioColumns.ARTIST);
  107. int indexTotalTime = mMusicCursor.getColumnIndex(MediaStore.Audio.AudioColumns.DURATION);
  108. int indexPath = mMusicCursor.getColumnIndex(MediaStore.Audio.AudioColumns.DATA);
  109. int indexAlbum = mMusicCursor.getColumnIndex(MediaStore.Audio.AudioColumns.ALBUM);
  110. for (mMusicCursor.moveToFirst(); !mMusicCursor.isAfterLast(); mMusicCursor.moveToNext()) {
  111. String mTitle = mMusicCursor.getString(indexTitle);
  112. String mArtist = mMusicCursor.getString(indexArtist);
  113. String mTotalTime = mMusicCursor.getString(indexTotalTime);
  114. String mPath = mMusicCursor.getString(indexPath);
  115. String mAlbum = mMusicCursor.getString(indexAlbum);
  116. if (mArtist.equals("<unknown>")) {
  117. mArtist = "未知歌手";
  118. }
  119. if (mAlbum.equals("<unknown>")) {
  120. mAlbum = "未知专辑";
  121. }
  122. Music music = new Music(++number, mTitle, mArtist, mPath, mTotalTime, mAlbum);
  123. mainMusicList.add(music);
  124. }
  125. }
  126. }
  127. }
  128. //设置适配器并初始化listView
  129. private void initListView() {
  130. List<Map<String, String>> list_map = new ArrayList<>();
  131. Map<String, String> map;
  132. SimpleAdapter simpleAdapter;
  133. for (Music music : mainMusicList) {
  134. map = new HashMap<>();
  135. map.put("id", String.valueOf(music.getId()));
  136. map.put("title", music.getName());
  137. map.put("artist", music.getArtist() + " - " + music.getAlbum());
  138. list_map.add(map);
  139. }
  140. String[] from = new String[]{"id", "title", "artist"};
  141. int[] to = {R.id.listview_tv_id, R.id.listview_tv_title_item, R.id.listview_tv_artist_item};
  142. simpleAdapter = new SimpleAdapter(this, list_map, R.layout.listview, from, to);
  143. list.setAdapter(simpleAdapter);
  144. }
  145. //列表中没有歌曲,则设置按钮不可用,并提醒用户
  146. private void checkMusicFile() {
  147. if (mainMusicList.isEmpty()) {
  148. imgBtn_Next.setEnabled(false);
  149. imgBtn_Stop.setEnabled(false);
  150. imgBtn_PlayOrPause.setEnabled(false);
  151. imgBtn_Previous.setEnabled(false);
  152. Toast.makeText(getApplicationContext(), "当前没有歌曲文件", Toast.LENGTH_SHORT).show();
  153. } else {
  154. imgBtn_Previous.setEnabled(true);
  155. imgBtn_Stop.setEnabled(true);
  156. imgBtn_PlayOrPause.setEnabled(true);
  157. imgBtn_Next.setEnabled(true);
  158. }
  159. }
  160. //读取音乐文件
  161. private void load(int number) {
  162. try {
  163. player.reset();
  164. player.setDataSource(MusicList.getMusicList().get(number).getPath());
  165. player.prepare();
  166. } catch (IOException e) {
  167. e.printStackTrace();
  168. }

五、测试数据及其结果分析

这是音乐播放器代码生成的apk如图2,单击即会出现页面如图3。

点击Android stdio菜单Build->Generate Signed APK,会弹出窗口。此时创建密钥库及密钥,创建后会自动选择刚创建的密钥库和密钥。点击“Next”按钮,选择保存路径后,点击“Finish”按钮完成。即可得到一个apk。

打开音乐播放器是软件会自动扫描手机中的音乐文件并添加至播放列表。

首先,initMusicList()会初始化音乐列表并读取其相关信息到指定变量当中。

然后,load()会自动读取音乐文件。

六、问题及解决方法

6.1 问题1: 不知道如何扫描手机中的音乐

解决方法

经过查询一番资料过后,确认android开发中本身拥有这种功能。于是写出如些代码来扫描出手机上的歌曲信息。

首先是获取歌曲信息。然后遍历歌曲信息将其存储到自己定义的Music实体类中。最后再用ListView组件进行展示信息列表。

6.2 问题二:生成apk时密钥解析错误

解决方法

安装目录\jdk1.8.0_131\jre\bin;在此目录下进入DOS命令窗口,使用命令keytool -genkey -alias demo.keystore -keyalg RSA -validity 20000 -keystore demo.keystore,生成密钥后将签名密钥和原apk文件放在相同目录了下;用DOS进入这个目录输入命令:jarsigner -verbose -keystore demo.keystore -signedjar LaTu_resign.apk -digestalg SHA1 -sigalg MD5withRSA LaTu.apk demo.keystore。

最后,会在这个目录生成重新签名apk文件。

6.3 问题三:安装虚拟手机时出现乱码

解决方法

注销原账号,注册以全英文为用户名的账号即可。

6.4 问题四:设置音量时,只能显示当前音量,而不能直接在软件中进行调节

解决方法

设置一个滑动条,使得能够在软件中直接调节音量,并且通过 this.setVolumeControlStream(AudioManager.STREAM_MUSIC);来设置滑动条最大值,并且通过this.getSystemService(Context.AUDIO_SERVICE);设置当前调整音量只针对媒体音量。

6.5 问题五:设计睡眠模式的时候不知道能从哪方面入手

解决方法

最后使用java提供的Timer类,其中含有定时器功能,在用户设置定时多少分钟后,然后再将分钟转换成毫秒进行监听。等用户设置的时间到达后,就会触发关闭APP操作。

6.6 问题六:设计睡眠模式的时候,一开始用的是弹出框来标记睡眠模式的开启,但这样就等于睡眠模式期间不能进行上一首下一首歌曲的切换等功能

解决方法

使用一个图标来显示睡眠模式是否开启,若开启,则显示图标,若未开启,则隐藏图标。使用了如下代码:

  1. if (sleepMode == MainActivity.ISSLEEP) {
  2. imageViewSleep.setVisibility(View.VISIBLE);
  3. } else {
  4. imageViewSleep.setVisibility(View.INVISIBLE);
  5. }

6.7 问题七:Android studio的配置问题,即gradle文件和Haxm文件的安装问题

解决方法:gradle文件如果自动下载是通过外网连接十分缓慢,所以要自己手动下载配置到文件夹中,而Haxm文件路径文件名必须为英文否则无法下载。

6.8 问题八:无法解决音乐的快进和倒退功能

解决方法

通过CSDN上相关博客,通过时间和线程相结合实现进度条位置跳转从而完成快进和倒退功能。其中主要函数为进度条位置跳转函数seekTo()和进度条初始化和更新函数initSeekBarHandler()。

6.9 问题九:不清楚相关播放功能的响应函数与组件的结合

解决方法

通过大量阅读之前别人写的音乐文件的代码,通过广播信号接收函数bindStatusChangedReceiver(),接收播放器状态更新的广播类StatusChangedReceiver和接收广播命令的内部类CommandReceiver实现组件和响应函数即stop()、randomplay()等函数的结合。

6.10 问题十:随机播放模式的实现

解决方法

通过随机函数random()构造随机音乐编号并通过play()函数播放。

七、总结

对于本次的音乐播放器的实现,过程可以说是异常的艰辛。

首先是环境的配置,组内的两个组员的电脑没有办法打开Android stdio界面设计的功能,而我的电脑因为用户名是中文的,要先注销账号和c盘,再注册新的账号重新下载软件,废了好大的时间才将我的电脑配置好。

然后,就是他们将代码发过来整合的时候总是报错,对于我来说,有时候根本无法看懂其代码,只能两个人同时改代码,导致播放器的效率极低。

最后,是歌词功能的实现。歌词功能本因是我来做,在歌词的获取上,可以从从本地一种是通过网络上提供的API获取,但是我不太懂这个,于是我找了一个歌词迷的API http://api.geci.me/en/latest/ 。这个API是根据歌名查询的,但是有的歌曲名可能会有空格,这样会影响获取歌词,所以,需要把空格都去掉。但是,问题就出现在这,因为这个空格问题,导致我每首歌曲查询都是没有歌词。在调试了很长时间后,我发现根本就没有办法,反而报的错越来越多,于是我就放弃了这个功能。

以上是我的个人感悟,而此次实验给我的经验就是让我学会了如何使用Android stdio这个软件,如何扫描本地文件,如何生成apk。也给了我一个教训就是电脑的用户名最好用英文,这样可以省去许多的麻烦。

对于这次实验,我能给的建议不是很多。但最重要的是要善于运用搜索引擎,我遇到的许多问题往往别人也遇到过,他们把这些问题发在网上就有很多的人去回答,而看看这些回答可以找到自己错误的原因。就像之前的歌词问题,一开始,我连空格问题都没有注意,然后在网络上看到了这个问题该怎么解决,虽然没有解决成功,但终究是学到了一些知识。

上传的附件 cloud_download 源程序.zip ( 32.68mb, 4次下载 ) cloud_download 音乐播放器.docx ( 768.19kb, 3次下载 )
error_outline 下载需要13点积分

发送私信

2
文章数
2
评论数
最近文章
eject