MP3歌词的同步与拖拽设计

原文地址:http://blog.csdn.net/mc_hust/article/details/51534901

自从准备毕业论文开始,就没写过博客了,关注量也明显呈下滑趋势(虽然本来就少)。到现在已经入职一个多月了,抽空把之前做的一个项目整理一下,算是毕业后的第一篇博客吧。


关于Mp3播放器,网上有各种实现方法,但是对于歌词的同步以及滑动更改播放进度的讲解却少之又少,所以我这里重点放在歌词的设计上(需要完整代码的朋友,可以在评论中留下邮箱,我会尽快回复),关于Mp3的“播放\切歌\暂停”以及“随机\顺序\单曲”播放等常用功能应该还是比较好做的。下面看看效果:

  • 主界面如下图:

    图1 - 主界面
    图1 - 主界面

  • 右滑之后进入歌词界面:

    图2 - 右滑进入歌词界面
    图2 - 右滑进入歌词界面

  • 点击右上角那个大设置按钮:

    设置界面
    图3 - 设置界面


整个项目主要涉及到以下知识点:
- ViewPager
- Service与Activity通信
- Broadcast
- ContentResolver
- PreferenceActivity
- MediaPlayer
以上几个知识点大家应该比较熟悉,,四大组件全用上了,个人觉得这是个比较好的练手项目。下面从播放开始看吧。


1、MP3播放器Service

作为播放器,固然是需要能够支持后台播放的,所以在启动播放之前,需要开启service。为了方便Activity与Service通信,这里通过bindService方法开启Service,代码如下:

bindService(new Intent(MainActivity.this, PlayService.class), connection, Context.BIND_AUTO_CREATE);

其中connection是Servive的一个回调方法,在里面获取Mp3Binder:

private ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {PlayService.Mp3Binder binder = (PlayService.Mp3Binder) service;player = new Mp3Player(binder.getService(), musicInfos);}@Overridepublic void onServiceDisconnected(ComponentName name) {}
};

上面有个player,这个就是对播放器播放、暂停、切歌等操作的一个封装类,下面来看看:


2、Mp3的播放、暂停、切歌

为了方便使用,将Mp3的播放操作封装到Mp3Player类中,在里面我实现了Mp3的各种常用操作,以及循环、单曲、顺序播放等常用播放模式,通过此类与Service通信,即可完成对MediaPlayer的操作。


3、MediaPlayer的使用

MediaPlayer的使用应该还是很简单的,如果没有做过MediaPlayer开发的朋友,需要注意几个问题:
1. 在播放之前一定要先重置、准备。调用的顺序为:reset、setDataSource、prepare、start。
2. 由于播放的歌曲通常是在SD卡上,记得要申明权限:



3. 因为涉及到搜索歌词、以及随机播放的时候需要计算下一首歌,那么我们分别需要捕捉播放开始和播放结束的信号,可以使用两个监听器完成,如下:

mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {sendBroadcast(new Intent(MainActivity.Mp3Receiver.ACTION_NEW));}
});
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {sendBroadcast(new Intent(MainActivity.Mp3Receiver.ACTION_END));}
});

这里我通过广播的方式将“开始播放”和“结束播放”两个信号传递出去。


4、获取歌曲列表

说了这么多,下面开始搜歌吧。这里用到Android的ContentProvider,Android系统会搜索手机里所有的音频文件,并放在MediaStore下面,我们要做的就是从这里面拿出想要的数据。通过

context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);

可以拿到列表的cursor,然后在当中去逐条获取信息即可。把每一个音频文件视为一个对象,可以如下定义音频对象:

class MusicInfo {long id;String title;String artist;String duration;int durationInSeconds;long size;String data;long albumId;@Overridepublic boolean equals(Object o) {data = data.replace("file://", "");return data.equals(((MusicInfo) o).data);}
}

这样从Cursor中获取数据之后填写到上面MusicInfo中就可以了,代码示意如下:

private static List<MusicInfo> getMusicInfoList(Context context) {Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);List<MusicInfo> list = new ArrayList<>();int count = cursor.getCount();while (count-- > 0) {cursor.moveToNext();if (0 == cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC))) {continue;}MusicInfo info = new MusicInfo();info.id = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media._ID));info.artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));long durationSeconds = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION)) / 1000;info.durationInSeconds = (int) durationSeconds;info.duration = durationSeconds % 60 < 10 ? durationSeconds / 60 + ":0" + durationSeconds % 60 : durationSeconds / 60 + ":" + durationSeconds % 60;info.size = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE));info.title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));info.data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));info.albumId = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));list.add(info);}return list;
}

这样拿到一个list然后设置到ListView中就可以完成歌曲列表的显示了。


5、搜索歌词

搜索歌词的原理其实就是在当前歌曲目录下去搜索同名的.lrc文件,然后从中读入数据流进行解析,歌词的解析可以参考lrc歌词的协议自行完成(需要完整代码可以在下面留下您的邮箱)。


6、歌词部分

接下来就是歌词的同步与歌词的滑动了,网上对于同步的实现大多是采用自定义一个TextView,然后再onDraw当中去用Paint画笔来画出歌词。这样做对于同步显示来讲非常容易,但是如果想让他在切换歌词的时候平滑移动以及拖拽歌词改变播放进度这都是比较麻烦的。因此这里我采用ListView来做歌词,这样平滑移动和滑动监听都比较方便。

由于需要将歌词放在屏幕中央,所以需要提前计算出屏幕中央是ListView的第几个Item,然后在前后依次留相应数据的空白。例如第五个item在中间,则在设置歌词数据的时候需要在前后分别留5个空白(示意代码,不建议这么写):

public void setLrcList(List<Lrc> lrcList) {//设置歌词内容this.lrcList = lrcList;//在歌词后留白lrcList.add(new Lrc());lrcList.add(new Lrc());lrcList.add(new Lrc());lrcList.add(new Lrc());lrcList.add(new Lrc());lrcList.add(new Lrc());//在歌词前留白lrcList.add(0, new Lrc());lrcList.add(0, new Lrc()); lrcList.add(0, new Lrc());lrcList.add(0, new Lrc());lrcList.add(0, new Lrc());lrcList.add(0, new Lrc());}
6.1 同步平滑更新歌词

通过update方法封装更新功能:

/*** 更新歌词内容** @param position 当前歌曲播放的时间*/
public void update(int position) {if (!isTouching) {adapter.notifyDataSetChanged();isAutoScroll = true;lvLrc.smoothScrollToPositionFromTop(adapter.update(position) - 4, 0, 1000);   //减4是保证当前这句歌词能显示在正中间}
}
  • 这里对ListView的滑动没有用到smoothScrollToPosition(int position);原因是这个函数仅仅是保证position的那个item会显示出来,而我们想要的效果是让他显示到正中间,所以只能用smoothScrollToPositionFromTop,让第前四句歌词显示在最顶端来实现效果。
  • adapter.update(position):这个方法的作用是获取歌曲播放到position时间的时候是第几句歌词,从而让他显示在中间,代码如下:
public int update(int position) {for (int i = 0; i < lrcList.size() - 1; i++) {//判断当前播放时间是否在歌词的第一句和最后一句歌词时间内if (position >= lrcList.get(i).getLrcTime() && position < lrcList.get(i + 1).getLrcTime() || position < lrcList.get(0).getLrcTime()) {index = i;break;}//如果时间超过了最后一句歌词,则停留在最后一句歌词else if (position > lrcList.get(lrcList.size() - 1).getLrcTime()) {index = lrcList.size() - 1;}}    return index;
}

这类似一个顺序查找算法,当然朋友们可以采用二分查找等其他算法提高效率。

这里实现的界面是一个ViewPager,第一页是歌曲列表,右滑到第二页是歌词。效果见上图

6.2 拖拽歌词改变播放进度

这部分主要是对歌词布局,即ListView的触摸监听操作,采用listView.setOnTouchListener来实现,先来看看这部分代码:

lvLrc.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:isTouching = true;break;case MotionEvent.ACTION_UP:int time = lrcList.get(lvLrc.getFirstVisiblePosition() + 5).getLrcTime();((MainActivity) activity).resume(time / 1000);isTouching = false;break;case MotionEvent.ACTION_CANCEL:isTouching = false;break;}return false;}});

主要是在ACTION_UP的时候进行操作,计算出当前播放的歌词的时间字段,然后通过service控制播放进度(resume中封装了对service的操作)。可以看到,在ACTION_DOWN和ACTION_CANCEL中也做了操作,主要是设置isTouching的值。这是为了防止在我们正在拖拽歌词的过程中,由于歌词同步作用导致当前歌词改变从而使歌词的ListView自动滑动。为了防止这个矛盾的出现,在歌词同步函数(update)中需要先检查isTouch的值,然后决定是否要进行自动同步(代码见6.1)。


7、设置界面PreferenceActivity

设置界面几乎是所有的App都要用到的,PreferenceActivity就是专门为设置界面打造的,而Android原生代码中几乎所有的设置界面也都是通过这个完成的。PreferenceActivity的使用方法网上有很多,他的使用与一般的布局类似,主要有以下几种类型:
* ListPreference 列表项菜单
* EditTextPreference 编辑框菜单
* SwitchPreference 开关菜单
本项目中就使用了以上几种菜单项,其余的也大同小异。我们可以对菜单项按功能进行分组,每一组是一个PreferenceCategory,而所有的PreferenceCategory都属于一个PreferenceScreen,这样的层级关系非常明确,具体的菜单布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"    android:title="设置"><PreferenceCategory android:title="播放模式"><ListPreference
            android:defaultValue="单曲循环"android:entries="@array/play_mode"android:entryValues="@array/play_mode_value"android:key="@string/key_play_mode"android:title="选择播放模式" /></PreferenceCategory><PreferenceCategory android:title="歌词设置"><ListPreference
            android:entries="@array/lrc_color"android:entryValues="@array/lrc_color_value"android:key="@string/key_lrc_color"android:title="歌词颜色" /><ListPreference
            android:entries="@array/lrc_size"android:entryValues="@array/lrc_size_value"android:key="@string/key_lrc_size"android:title="歌词大小" /></PreferenceCategory><PreferenceCategory android:title="定时关机"><EditTextPreference
            android:summary="将在设置的分钟数后关机"android:title="请输入关机时间" /></PreferenceCategory><PreferenceCategory android:title="摇一摇切歌"><SwitchPreference android:title="开启摇晃切歌" /></PreferenceCategory>

Activity的代码也非常简单:

package com.example.machao10.mp3;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.SwitchPreference;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;public class SettingsActivity extends PreferenceActivity {ListPreference listPlayMode, listLrcSize, listLrcColor, listRing, listNotification, listSms;EditTextPreference etAutoShutdown;SwitchPreference switchShake;private void initPreference() {listPlayMode = (ListPreference) findPreference(getString(R.string.key_play_mode));SettingsChangeListener listener = new SettingsChangeListener();listPlayMode.setOnPreferenceChangeListener(listener);}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);addPreferencesFromResource(R.xml.settings);initPreference();}class SettingsChangeListener implements Preference.OnPreferenceChangeListener {@Overridepublic boolean onPreferenceChange(Preference preference, Object newValue) {String key = preference.getKey();return true;}}
}

当然,以上只是对设值界面进行了显示,还需要完成相应的逻辑和用户设置的持久化,这个大家可以参考PreferenceActivity的具体用法,这里我就不展开讲了,需要完整开发源码的,可以在下面留下邮箱,我会及时给您回复的。


好了,mp3播放器就讲到这里,主要是从逻辑结构上做的梳理,然后针对部分细节进行展开,并没有将完整的代码做一个串接,主要还是考虑到关于Mp3的功能网上有很多资料,只是在歌词那一块应该还是很空白的。也希望我的这个歌词方案能够给大家带来一些方便,同时大家有什么好的建议欢迎讨论~

——超低空

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/53485.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用leangoo领歌单团队敏捷开发项目管理

概述 单团队敏捷开发主要是针对10人以下、只有一个Scrum团队的小型产品或项目的敏捷开发。 对于小型团队来说&#xff0c;在Leangoo中创建一个单团队敏捷开发项目就可以很好地支持团队产品或项目的开发。 适用场景 适用于单个团队进行Scrum敏捷开发协作&#xff0c;Leangoo项目…

Amper Music:AI创意音乐工具

【产品介绍】 Amper Music 是一家位于美国纽约的人工智能音乐技术公司&#xff0c;成立于2014年。 Amper Music是一个AI创意音乐工具&#xff0c;能让任何人为自己的内容制作原创音乐。无论你需要为视频、播客或互动内容配乐&#xff0c;Amper Music都能提供一个简单而强大的解…

【NLP】Transformer—用注意力机制改进自然语言处理

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

CLIP对比图文预训练 (Contrastive Language-Image Pretraining)论文阅读笔记

任务&#xff1a; video captioning&#xff0c; 视频描述生成&#xff0c;简单来说就是给定一段视频&#xff08;目前以几秒到几分钟的短视频为主&#xff09;&#xff0c;计算机输出描述这段视频的文字&#xff08;目前以英文为主&#xff09;。往往一个视频对应多个人工标注…

干货|免费文本语料训练数据集

关于Word2Vec&#xff0c;上篇文章文本分类特征提取之Word2Vec中已有还算详尽的叙述。简单总结下&#xff1a;word2vec是Google在2013年提出的一款开源工具&#xff0c;其是一个Deep Learning模型&#xff08;实际上该模型层次较浅&#xff0c;严格上还不能算是深层模型&#x…

​GENIUS: 根据草稿进行文本生成的预训练模型,可用于多种NLP任务的数据增强...

©PaperWeekly 原创 作者 | 郭必扬 单位 | 上海财经大学信息管理与工程学院AI Lab 论文标题&#xff1a; GENIUS: Sketch-based Language Model Pre-training via Extreme and Selective Masking for Text Generation and Augmentation 论文作者&#xff1a; Biyang Guo,…

UI自动化控制PC端微信获取当前群聊的群友列表

在一个月前,某个群友在获取整个群的群友信息时遇到了一点问题: 对整个群进行群友数据获取经测试确实有点棘手,下面我将我的解决过程公布给大家。 基础教程详见: https://blog.csdn.net/as604049322/article/details/121391639 人工打开要抓取的群聊窗口后,首先获取微信窗…

企业微信接入群聊机器人详细步骤

目录 一、 创建群机器人 二、机器人配置 三、机器人信息推送 四、线上使用 五、推送效果 一、 创建群机器人 先选择一个企业微信群右键添加机器人完善机器人的头像、名称即可 二、机器人配置 查看生成的机器人webhook地址点击地址&#xff0c;里面可以查看文档和一些简单的配…

每日学术速递2.1

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.Cv 1.SeaFormer: Squeeze-enhanced Axial Transformer for Mobile Semantic Segmentation 标题&#xff1a;SeaFormer:用于移动语义分割的挤压增强型轴向变换器 作者&#xff1a; Qian…

chatgpt赋能python:Python轨迹可视化:用数据讲故事

Python轨迹可视化&#xff1a;用数据讲故事 介绍 随着物联网、智能城市等领域的发展&#xff0c;越来越多的数据被收集下来并存储在数据库中。这些数据对于决策者来说是非常重要的&#xff0c;但是如何将这些数据进行展示和分析呢&#xff1f;这时候Python轨迹可视化就可以派…

AutoCV第八课:3D基础

目录 3D基础注意事项一、2023/5/11更新二、2023/5/15更新前言1. nuScenes数据集2. nuScenes数据格式3. 点云可视化4. 点云可视化工具5. 点云可视化(补充)总结 3D基础 注意事项 一、2023/5/11更新 新增工具可视化点云&#xff0c;即第 4 节内容 二、2023/5/15更新 经杜老师…

chatgpt赋能python:Python轨迹绘制:让数据可视化更易懂

Python轨迹绘制&#xff1a;让数据可视化更易懂 在数据可视化中&#xff0c;轨迹图应该是最为常见的一种类型了。轨迹图通过展示物体或用户在一定时间范围内移动的路径&#xff0c;帮助我们更加直观地理解和分析数据。 在Python中&#xff0c;我们可以使用多种库来绘制轨迹图…

chatgpt赋能python:Python坐标表示:让您的数据可视化更加精确!

Python坐标表示&#xff1a;让您的数据可视化更加精确&#xff01; 在数据可视化中&#xff0c;通过准确的坐标表示数据点的位置是非常重要的。Python作为最受欢迎的编程语言之一&#xff0c;有多种方法来表示坐标。在本文中&#xff0c;我们将深入探讨Python中坐标表示的不同…

chatgpt赋能python:Python色板介绍:让数据可视化更加迷人

Python色板介绍&#xff1a;让数据可视化更加迷人 在数据可视化中&#xff0c;合适的配色方案是非常重要的。Python色板是一种常用的工具&#xff0c;可以帮助数据分析师和科学家创建漂亮、易于阅读的图表。本文将为大家介绍Python色板&#xff0c;包括一些主流的Python色板、…

人生意义

一块蛋糕最终还是会被吃完&#xff0c;那么你吃它的目的是什么&#xff1f; 1.你不能因为一样美好的东西最终会消失而否定它的意义。 2.生命是一场馈赠。它和所有我们收到的其它礼物&#xff0c;比如蛋糕一样&#xff0c;本身并没有意义&#xff0c; 但如果你能享受它的美好…

程序员:我终于知道post和get的区别

效率工具传送门 推荐20套实战源码 程序员你可以考虑安装的15款谷歌插件 99%的人不知道搜索引擎的6个技巧 12款好用的Visual Studio插件&#xff0c;最后一款良心推荐 IT界知名的程序员曾说&#xff1a;对于那些月薪三万以下&#xff0c;自称IT工程师的码农们&#xff0c;其…

chatgpt赋能python:Python通过IP连接同一局域网中的电脑

Python通过IP连接同一局域网中的电脑 在日常工作中&#xff0c;我们常常需要在本地局域网内进行电脑之间的通信&#xff0c;这时候就需要借助一些工具来进行IP连接。而对于Python编程来说&#xff0c;实现这个过程也非常简单。 什么是IP连接 IP连接指的是使用IP地址和端口号…

chatgpt赋能python:Python获取电脑IP地址方法与实例

Python获取电脑IP地址方法与实例 什么是IP地址 IP地址是一个数字标识符&#xff0c;是指在Internet协议中用作网络接口标识和定位地址的32位二进制数&#xff0c;通常表现为四个十进制数&#xff0c;每个数用“.”分隔&#xff0c;如192.168.0.1。在不同的网络协议中&#xf…

win10计算机ip如何更改,Win10本地连接ip怎么更改_Win10怎么更改ip地址?-192路由网...

问&#xff1a;Win10本地连接IP地址怎么更改&#xff1f; 本人是电脑小白&#xff0c;新买的电脑&#xff0c;是Win10系统&#xff1b;请问Win10电脑中的IP地址在哪里更改&#xff1f; 答&#xff1a;Win10系统IP地址需要在“本地连接”的 属性 中进行更改的。所以&#xff0c;…

chatgpt赋能python:Python获得电脑IP地址

Python获得电脑IP地址 在进行网络编程时&#xff0c;通常需要获取本机的IP地址。对于Python开发者来说&#xff0c;获取电脑IP地址可能是开发过程中常见需求之一。本文将介绍如何使用Python获取电脑IP地址&#xff0c;以及其中的一些常见问题和解决方案。 什么是IP地址&#…