android歌词效果,自定义View:Android歌词控件

TicktockMusic 音乐播放器项目相关文章汇总:

简介

之前做 TicktockMusic 音乐播放器,一个必要的需求肯定是歌词,在 github 上找了几个,发现或多或少都有点不满足需求,所以就自己动手写了一个,本篇文章主要介绍下实现的原理。

先附上项目地址和效果图:

效果图:

ab735509cc74

image

需求

歌词的需求我想大家都很清楚,简单的话,直接打开一个音乐播放器查看一下。我们打开后分析一下歌词的功能:歌词完整的显示出来、当前歌词变色、可以根据时间而进行定位、可以手动滑动、滑动后显示一个指示器、点击指示器播放进度跳转、滑动时指示器变色等等。OK,我们自己写歌词控件,这些功能也是必不可少的,接下来就逐步分析下实现的过程。

实现

歌词解析

歌词显示

滑动处理

指示器

基本实现就是这几个过程,接下来一步步的分析。

歌词解析

首先,我们在网上下载一个歌词,即以 lrc 为后缀的文件。比如海阔天空这首歌的歌词,我们用记事本或者其他工具打开后就可以看到具体的歌词内容,如下:

[ti: 海阔天空]

[ar:黄家驹]

[al:乐与怒]

[by:mp3.50004.com]

[00:00.00]Beyond:海阔天空

[01:40.00][00:16.00]今天我寒夜里看雪飘过

[01:48.00][00:24.00]怀著冷却了的心窝飘远方

[01:53.00][00:29.00]风雨里追赶

...

[00:42.00]多少次迎著冷眼与嘲笑

[00:49.00]从没有放弃过心中的理想

[00:54.00]一刹那恍惚

...

可以看到,歌词主要包含歌名、歌手、专辑、作者等头元素,以及歌词的主体内容,我们需要处理的就是主体的歌词内容。首先,歌词是一行一行的文本,其次,每行的文本都包含时间标签和具体的一行歌词,我们首先将歌词解析为一行行的数据。

InputStreamReader isr = null;

BufferedReader br = null;

try {

isr = new InputStreamReader(inputStream, CHARSET);

br = new BufferedReader(isr);

String line;

while ((line = br.readLine()) != null) {

//此处的 line 即为一行行的文本

//parseLrc 方法为解析单行

List lrcList = parseLrc(line);

if (lrcList != null && lrcList.size() != 0) {

lrcs.addAll(lrcList);

}

}

sortLrcs(lrcs);

return lrcs;

}catch ...

解析为一行行的文字后,就需要具体的处理单行的文字了,我们可以看到,大部分歌词包含两种格式,即单个时间标签和多个时间标签,这里可以采用正则表达式来匹配文字,正则表达式为 (([\d{2}:\d{2}.\d{2}])+)(.*)

[01:53.00][00:29.00]风雨里追赶 //多个时间标签

[00:42.00]多少次迎著冷眼与嘲笑 //单个时间标签

接下来根据正则表达式来解析单行歌词

private static List parseLrc(String lrcLine) {

if (lrcLine.trim().isEmpty()) {

return null;

}

List lrcs = new ArrayList<>();

Matcher matcher = Pattern.compile(LINE_REGEX).matcher(lrcLine);

if (!matcher.matches()) {

return null;

}

String time = matcher.group(1);

String content = matcher.group(3);

Matcher timeMatcher = Pattern.compile(TIME_REGEX).matcher(time);

while (timeMatcher.find()) {

String min = timeMatcher.group(1);

String sec = timeMatcher.group(2);

String mil = timeMatcher.group(3);

Lrc lrc = new Lrc();

if (content != null && content.length() != 0) {

lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000

+ Long.parseLong(mil) * 10);

lrc.setText(content);

lrcs.add(lrc);

}

}

return lrcs;

}

这样,第一步就完成了,歌词解析完成后得到歌词的数据集合,每个元素都包括时间和内容。

歌词显示

歌词显示的思路就是将歌词一行行的画出来,我们首先假设屏幕足够大,那么只需要定位第一行歌词的位置,画出来第一行歌词,然后逐行下移一个固定的距离,再画出下一行歌词,依次类推,整个歌词内容就会全部画在画布上了。依照这个思路,我们可以先画出来文字。

//此处为伪代码

float y = getLrcHeight() / 2;

float x = getLrcWidth() / 2 + getPaddingLeft();

for (int i = 0; i < getLrcCount(); i++) {

if (i > 0) {

y += textHeight + mLrcLineSpaceHeight;

}

...

canvas.drawText(text, x, y, mPaint);

}

画出来文字的思路就是这样,首先从屏幕的中间开始,然后纵坐标每次增加文字的高度与距离之和,依次画出来每行文字。这样,假如屏幕足够大的话,那么所有的歌词就会从屏幕中间开始,依次向下一行行的显示出来。但是,我们的屏幕不可能是无限大的。首先,假如一行歌词很长的话,canvas.drawText() 的效果会是屏幕覆盖掉多余的 text 文字,所以当一行文字超过我们设置的 View 最大宽度时,最理想的方法就是多余的部分换行,就像 TextView 一样。所幸的是,Android 中给我们提供了方法,那就是 StaticLayout ,StaticLayout 用法很简单,我们使用它来替代 canvas.drawText(),下面是基本用法。

private void drawLrc(Canvas canvas, float x, float y, int i) {

mTextPaint.setTextSize(mLrcTextSize);

String text = mLrcData.get(i).getText();

StaticLayout staticLayout = new StaticLayout(text, mTextPaint, getLrcWidth(),

Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);

canvas.save();

canvas.translate(x, y - staticLayout.getHeight() / 2 - mOffset);

staticLayout.draw(canvas);

canvas.restore();

}

这样我们就能获取想要的效果了,文字一行行的排列,文字比较长的话,会自动换行到下一行。但是,这样仅仅是实现效果,在 onDraw() 方法中,我们应该尽量的避免新建对象,以免造成界面的卡顿,而 StaticLayout 需要实例化对象,所以这边需要我们手动优化一下。

因为使用 StaticLayout 后,一行文字的高度不再固定,所以 y 坐标不再累加固定的文字高度,而是上一行和下一行文字之和的一半+文字间距。代码如下:

for (int i = 0; i < getLrcCount(); i++) {

if (i > 0) {

y += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight;

}

drawLrc(canvas, x, y, i);

}

为了避免过多的实例化,在使用 StaticLayout 时,这里采用 map 进行缓存,创建过对象后缓存起来,后边就不需要再继续创建。

private void drawLrc(Canvas canvas, float x, float y, int i) {

String text = mLrcData.get(i).getText();

StaticLayout staticLayout = mLrcMap.get(text);

if (staticLayout == null) {

mTextPaint.setTextSize(mLrcTextSize);

staticLayout = new StaticLayout(text, mTextPaint, getLrcWidth(),

Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);

mLrcMap.put(text, staticLayout);

}

canvas.save();

canvas.translate(x, y - staticLayout.getHeight() / 2 - mOffset);

staticLayout.draw(canvas);

canvas.restore();

}

到这里,我们已经解决了水平方向的显示,但是垂直方向呢,垂直方向则利用滑动来解决,这也是歌词的基本需求之一。

滑动处理

歌词的滑动是做歌词控件的必然要求,包括根据音乐播放的进度进行自动的滑动,以及用户主动拖动的滑动,我们来逐个分析。

1、根据播放进度滚动

音乐的播放时间进度可以根据 MediaPlayer 来获取,在一首音乐播放的过程中,播放的进度是不断更新的,所以就需要我们根据这个不断更新的时间,来决定歌词滚动的位置。

我们需要比较不断更新的时间和每行歌词的时间,最接近或者相等时,就可以视作音乐播放的进度对应当前这一行歌词,所以需要获取播放时间对应的歌词行数。

private int getUpdateTimeLinePosition(long time) {

int linePos = 0;

for (int i = 0; i < getLrcCount(); i++) {

Lrc lrc = mLrcData.get(i);

if (time >= lrc.getTime()) {

if (i == getLrcCount() - 1) {假如时间大于最后一行歌词的时间,则行数为最后一行

linePos = getLrcCount() - 1;

} else if (time < mLrcData.get(i + 1).getTime()) {//否则若同时小于下一行,则行数为 i

linePos = i;

break;

}

}

}

return linePos;

}

获取行数之后,行数变化时,就可以利用动画,来让歌词进行滚动。

private void scrollToPosition(int linePosition) {

float scrollY = getItemOffsetY(linePosition);//将要滚动的一行的偏移量

final ValueAnimator animator = ValueAnimator.ofFloat(mOffset, scrollY);

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

mOffset = (float) animation.getAnimatedValue();

invalidateView();

}

});

animator.setDuration(300);

animator.start();

}

此处最重要的属性就是 mOffset ,mOffset 是为了决定歌词偏移量而定义的一个属性, mOffset 的取值是在原有值和目标行的偏移量之间,由动画控制其变化。假如向下滑动,初始为0,则滚动到第二行歌词,mOffset 就是从 0 到 getItemOffsetY(1) 的过程。 getItemOffsetY(i) 就是第 i 行的偏移量。

private float getItemOffsetY(int linePosition) {

float tempY = 0;

for (int i = 1; i <= linePosition; i++) {

tempY += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight;

}

return tempY;

}

然后,再根据播放进度,进行不断的更新。

public void updateTime(long time) {

if (isLrcEmpty()) {

return;

}

int linePosition = getUpdateTimeLinePosition(time);

if (mCurrentLine != linePosition) {

mCurrentLine = linePosition;

ViewCompat.postOnAnimation(LrcView.this, mScrollRunnable);

}

}

private Runnable mScrollRunnable = new Runnable() {

@Override

public void run() {

scrollToPosition(mCurrentLine);

}

};

到此为止,我们已经完成了歌词的自动滚动功能。

2、滑动事件处理

仅仅有自动滚动是无法满足歌词的需求的,所以我们还需要控制歌词的滑动事件,让用户可以手动滑动歌词到某个位置。既然是手势的事件,那么就需要我们重写 onTouch 方法,处理不同的手势。

@Override

public boolean onTouchEvent(MotionEvent event) {

if (isLrcEmpty()) { //歌词为空,则默认事件

return super.onTouchEvent(event);

}

//速度跟踪

if (mVelocityTracker == null) {

mVelocityTracker = VelocityTracker.obtain();

}

mVelocityTracker.addMovement(event);

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

removeCallbacks(mScrollRunnable);

if (!mOverScroller.isFinished()) {

mOverScroller.abortAnimation();

}

mLastMotionX = event.getX();

mLastMotionY = event.getY();

isUserScroll = true;

isDragging = false;

break;

case MotionEvent.ACTION_MOVE:

float moveY = event.getY() - mLastMotionY;

if (Math.abs(moveY) > mScaledTouchSlop) {

isDragging = true;

isShowTimeIndicator = isEnableShowIndicator;

}

if (isDragging) {

float maxHeight = getItemOffsetY(getLrcCount() - 1);

if (mOffset < 0 || mOffset > maxHeight) {

moveY /= 3.5f;

}

mOffset -= moveY;

mLastMotionY = event.getY();

invalidateView();

}

break;

case MotionEvent.ACTION_CANCEL:

case MotionEvent.ACTION_UP:

handleActionUp(event);

break;

}

return true;

}

简单解释下上述代码,先忽略掉 VelocityTracker 和 OverScroller。在 ACTION_DOWN 时,记录下 x 和 y 的坐标;然后在 ACTION_MOVE 时,若拖动的距离大于触发滑动的最小值,则改变 mOffset 的值,然后刷新 View。当 mOffset < 0 或者 mOffset > maxHeight 即歌词已经滚动到顶部或者底部时,为了回弹的阻尼效果,将 moveY 的值大幅减小。

接下来介绍下手势抬起的事件,VelocityTracker 和 OverScroller 就是用于此处,在手势滑动抬起时,我们希望有一个 fling 的效果,Android 中的 OverScroller 可以简单的实现这种效果。

private void handleActionUp(MotionEvent event) {

//越界的处理

if (overScrolled() && mOffset < 0) {

scrollToPosition(0);

ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);

return;

}

if (overScrolled() && mOffset > getItemOffsetY(getLrcCount() - 1)) {

scrollToPosition(getLrcCount() - 1);

ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);

return;

}

mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);

float YVelocity = mVelocityTracker.getYVelocity();

float absYVelocity = Math.abs(YVelocity);

if (absYVelocity > mMinimumFlingVelocity) {

mOverScroller.fling(0, (int) mOffset, 0, (int) (-YVelocity), 0,

0, 0, (int) getItemOffsetY(getLrcCount() - 1),

0, (int) getTextHeight(0));

invalidateView();

}

releaseVelocityTracker();

if (isAutoAdjustPosition) {

ViewCompat.postOnAnimationDelayed(LrcView.this, mScrollRunnable, mTouchDelay);

}

}

当手势抬起时,计算下当前的手势速度,然后利用 mOverScroller.fling() 方法,在 computeScroll() 中改变 mOffset 的值即可。

@Override

public void computeScroll() {

super.computeScroll();

if (mOverScroller.computeScrollOffset()) {

mOffset = mOverScroller.getCurrY();

invalidateView();

}

}

这样,主动的手势功能也已经实现了。

指示器

用户手动滑动歌词的目的,很大一部分是为了滑动后能根据歌词来控制播放的进度,所以指示器也是一个不可或缺的功能。当用户滑动歌词时,显示指示器,歌词经过指示器的位置时变色,用户点击指示器按钮后,歌词跳转到这个位置,播放进度也到了这里。

首先要做的就是显示指示器以及歌词变色,这里就需要我们获取歌词在指示器的位置时,歌词的行数,因为指示器画在歌词的中间位置,所以某一行歌词的偏移量和 mOffset 的差值最小时,就可以看作这一行歌词经过了指示器。

public int getIndicatePosition() {

int pos = 0;

float min = Float.MAX_VALUE;

//itemOffset 和 mOffset 最小时,当前的位置

for (int i = 0; i < mLrcData.size(); i++) {

float offsetY = getItemOffsetY(i);

float abs = Math.abs(offsetY - mOffset);

if (abs < min) {

min = abs;

pos = i;

}

}

return pos;

}

然后在 onDraw() 中,画出来具体的特性。

if (isShowTimeIndicator) {

mPlayDrawable.draw(canvas); // 画出指示器的播放按钮

long time = mLrcData.get(indicatePosition).getTime();

float timeWidth = mIndicatorPaint.measureText(LrcHelper.formatTime(time)); //获取指示时间的文字长度

mIndicatorPaint.setColor(mIndicatorLineColor);

// 画出指示线

canvas.drawLine(mPlayRect.right + mIconLineGap, getHeight() / 2,

getWidth() - timeWidth * 1.3f, getHeight() / 2, mIndicatorPaint);

int baseX = (int) (getWidth() - timeWidth * 1.1f);

float baseline = getHeight() / 2 - (mIndicatorPaint.descent() - mIndicatorPaint.ascent()) / 2 - mIndicatorPaint.ascent();

mIndicatorPaint.setColor(mIndicatorTextColor);

//画出指示时间文字

canvas.drawText(LrcHelper.formatTime(time), baseX, baseline, mIndicatorPaint);

}

最后,处理用户点击事件,并且将当前行的歌词及时间进行回调,来控制播放进度。

if (isShowTimeIndicator && mPlayRect != null && onClickPlayButton(event)) {

isShowTimeIndicator = false;

invalidateView();

if (mOnPlayIndicatorLineListener != null) {

mOnPlayIndicatorLineListener.onPlay(mLrcData.get(getIndicatePosition()).getTime(),

mLrcData.get(getIndicatePosition()).getText());

}

}

//点击在按钮范围才响应

private boolean onClickPlayButton(MotionEvent event) {

float left = mPlayRect.left;

float right = mPlayRect.right;

float top = mPlayRect.top;

float bottom = mPlayRect.bottom;

float x = event.getX();

float y = event.getY();

return mLastMotionX > left && mLastMotionX < right && mLastMotionY > top

&& mLastMotionY < bottom && x > left && x < right && y > top && y < bottom;

}

这样,指示器的功能也就完成了。

总结

上述就是整个歌词控件绘制的流程,还有一些颜色变化等细节功能就不一一说明了,有兴趣可以看一看源码。这个控件我也已经封装成了一个自定义 View 的库,可以在 https://github.com/Lauzy/LyricView 这里看下具体的使用。欢迎讨论、欢迎 star。

参考:

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

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

相关文章

IDEA项目中 target 目录的作用

IDEA项目中 target 目录的作用 target是idea默认的编译路径&#xff0c;用来存放项目的&#xff1a;文件和目录、jar包、war包、class文件等。

java上传图片到target目录,jsp如何访问target里的图片路径

java上传图片到target目录&#xff0c;jsp如何访问target里的图片路径 昨天有个同学在做springmvc的图片上传&#xff0c;上传成功了却无法从网页中访问&#xff0c;我看了看上传后的路径&#xff0c;它用的idea编辑器&#xff0c;图片上传到了target里的目录&#xff0c;src目…

html中 为什么在页面点击提交后reset按钮就不起作用了

html中 为什么在页面点击提交后reset按钮就不起作用了 解释&#xff1a;reset的作用是恢复初始值&#xff0c;提交后页面重新加载&#xff0c;初始值已经是修改后的值了&#xff0c;并不是reset不起作用了。 你可以在提交后&#xff0c;修改几个值再reset试试&#xff0c;看是…

c:if标签不起作用

c:if标签因多余空格导致不起作用 <c:if test" ${monitorUserConfigure.monitorCycle 2 } ">selected</c:if>上面这段代码粗看没什么问题&#xff0c;但是在程序中始终不起作用。原因是test""表达式的两个引号和里面的内容有空格&#xff0c;…

RegExp:正则表达式对象

RegExp&#xff1a;正则表达式对象 1. 正则表达式&#xff1a;定义字符串的组成规则。 1. 单个字符:[] 如&#xff1a; [a] [ab] [a-zA-Z0-9_] 特殊符号代表特殊含义的单个字符: \d:单个数字字符 [0-9] \w:单个单词字符[a-zA-Z0-9_] 2. 量词符号&#xff1a; ?&#xff1a;表…

java文件下载出现文件名乱码解决办法

java文件下载出现文件名乱码解决办法 //IE、chrom、Firefox文件中文乱码问题public String processFileName(HttpServletRequest request, String fileNames) {String codedfilename null;try {String agent request.getHeader("USER-AGENT");if (null ! agent &a…

android view退出动画,android animation——view进来退出动画

在设计android项目的时候我们有时候需要对activity的进入退出做一些动画处理&#xff0c;虽然android自身已经做了动画处理&#xff0c;但是我们需要更加炫酷的动画就需要自己去写一些动画了。有时候不光activity&#xff0c;还有popupwindow或者自定义view都是需要动画的。那么…

bc8android汽车中控屛功能有哪些,丰田酷路泽中控台的8大功能 你们知道都是干什么用的?...

兰德酷路泽(参数|图片)中控台的8大功能 ①&#xff1a;高低速四驱调节高低速四驱调节系统&#xff0c;有着两个方向&#xff0c;L4和H4H4&#xff0c;指的是高速四驱&#xff0c;开启H4的时候适合走沙石路面&#xff0c;泥泞路面&#xff0c;雪地&#xff0c;沙石地等。最高车速…

关于java二维数组长度(length)的知识

关于java二维数组长度(length)的知识 二维数组的长度 //定义一个整型数组:3行4列 int a[][] new int[3][4]; //也可以 int[][] anew int[3][4]; //获取行数---3行 int lenY a.length; //获取列数---4列 int lenX a[0].length;其实很好理解&#xff0c;因为二维数组可以理解…

android 动态广告图片,android – 如何在动态壁纸的设置屏幕中添加一个admob广告视图?...

这是一个更简单的解决方案&#xff1a;创建一个显示单个广告的新首选项类型.然后,您可以在首选项的xml定义中包含该首选项类型,以显示一个或多个广告.自定义偏好类&#xff1a;public class AdmobPreference extends Preference{public AdmobPreference(Context context) {supe…

微服务和分布式的区别

微服务和分布式的区别 1.分布式 将一个大的系统划分为多个业务模块&#xff0c;业务模块分别部署到不同的机器上&#xff0c;各个业务模块之间通过接口进行数据交互。区别分布式的方式是根据不同机器不同业务。 上面&#xff1a;service A、B、C、D 分别是业务组件&#xff…

android关机背景,鍵盤消失后的Android白色背景

Video of the problem from a different user but its the same來自不同用戶的問題的視頻但是相同http://imgur.com/ca2cNZvI have a background image set as follows :我有一個背景圖像設置如下&#xff1a;.pane {background-image: url("../img/inner-banner-bg.jpg&q…

javax.servlet.ServletException: Circular view path []: would dispatch back to the current....

解决&#xff1a;javax.servlet.ServletException: Circular view path []: would dispatch back to the current… 原因&#xff1a; 当没有声明ViewResolver时&#xff0c;spring会注册一个默认的ViewResolver&#xff0c;就是JstlView的实例&#xff0c; 该对象继承自Inter…

android跌倒检测,Android跌倒检测

我正在使用加速度传感器实现跌倒检测,并创建以下代码.public void onSensorChanged(SensorEvent foEvent) {if (foEvent.sensor.getType() Sensor.TYPE_ACCELEROMETER) {double loX foEvent.values[0];double loY foEvent.values[1];double loZ foEvent.values[2];double l…

SpringBoot与SpringCloud的关系与区别

一、SpringBoot和SpringCloud简介 1、SpringBoot&#xff1a;是一个快速开发框架&#xff0c;通过用MAVEN依赖的继承方式&#xff0c;帮助我们快速整合第三方常用框架&#xff0c;完全采用注解化&#xff08;使用注解方式启动SpringMVC&#xff09;&#xff0c;简化XML配置&am…

android studio break,Android Studio IDE: Break on Exception

可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效&#xff0c;请关闭广告屏蔽插件后再试):问题:It seems my Android Studio does not want to break on any exception by default. Enabling break on "Any Exception" starts breaking within act…

SpringBoot怎么直接访问templates下的html页面

SpringBoot直接访问templates下的html问题 方法1&#xff1a;曾经&#xff1a; template下文件不允许直接访问 1、springboot项目默认是不允许直接访问template下的文件的&#xff0c;是受保护的。 所以想访问template下的html页面&#xff0c;我们可以配置视图解析器。 2、如…

php查到的内容追加到html,javascript - 请问php中如何将查询出来的结果数组转化成自己想要的格式,并在前台利用js输出到html中...

考试类型的表jx_exam_type&#xff0c;可后台添加内容考试成绩的表jx_result&#xff0c;可后台添加内容期中考试成绩表中的exam_id对应考试类型表中的id&#xff0c;也就是添加的成绩是属于期中还是期末然后使用php查询$sql"SELECT re.type, re.score, re.exam_id, et.ti…

DevOps(过程、方法与系统的统称)是什么

DevOps &#xff08;过程、方法与系统的统称&#xff09; DevOps&#xff08;Development和Operations的组合词&#xff09;是一组过程、方法与系统的统称&#xff0c;用于促进开发&#xff08;应用程序/软件工程&#xff09;、技术运营和质量保障&#xff08;QA&#xff09;部…

android 蓝牙sco stream_voice_call,android TTS输出总是要 A2DP_android_开发99编程知识库

大多数设备上我都有这样的工作。 下面是在语音呼叫流中使用蓝牙SCO代替A2DP启动TTS的部分。if (mTtsReady) {myHash new HashMap();myHash.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,"A2DP_Vol");OLD_AUDIO_MODE am2.getMode();if(SMSstream 1){if (am2.is…