Commit 2175c230 by konghaorui

补充音乐播放自动保存播放进度功能

parent c8e30390
......@@ -37,6 +37,9 @@ android {
}
multiDexEnabled true
ndk {
abiFilters "armeabi-v7a" // 指定要ndk需要兼容的架构(这样其他依赖包里mips,x86,armeabi,arm-v8之类的so会被过滤掉)
}
}
compileOptions {
......
......@@ -6,7 +6,6 @@ import android.content.Context;
import android.content.Intent;
import com.ydl.media.music.PlayService;
import com.ydl.media.music.utils.CoverLoader;
import com.ydl.ydlcommon.base.delegate.IAppLifecycles;
import com.ydl.ydlcommon.utils.YdlBuryPointUtil;
......@@ -26,7 +25,6 @@ public class DemoAppLifecycles implements IAppLifecycles {
public void onCreate(@NotNull Application application) {
//数据埋点初始化--一定要放在主进程中
YdlBuryPointUtil.init(application);
CoverLoader.Companion.get().init(application);
Intent intent = new Intent(application, PlayService.class);
application.startService(intent);
}
......
package com.ydl.component.music;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
......@@ -18,11 +17,11 @@ import com.ydl.media.music.AudioPlayer;
import com.ydl.media.music.OnPlayerEventListener;
import com.ydl.media.music.enums.PlayModeEnum;
import com.ydl.media.music.model.Music;
import com.ydl.media.music.utils.CoverLoader;
import com.ydl.media.music.utils.ScreenUtils;
import com.ydl.media.music.utils.ImageUtils;
import com.yidianling.common.tools.ToastUtil;
import java.util.Locale;
import java.util.Objects;
import butterknife.BindView;
import butterknife.ButterKnife;
......@@ -30,8 +29,8 @@ import butterknife.Unbinder;
/**
* 正在播放界面
* Created by wcy on 2015/11/27.
* Created by haorui on 2019-10-28 .
* Des:
*/
public class PlayFragment extends Fragment implements View.OnClickListener,
SeekBar.OnSeekBarChangeListener, OnPlayerEventListener {
......@@ -77,7 +76,6 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initSystemBar();
initPlayMode();
onChangeImpl(AudioPlayer.Companion.get().getPlayMusic());
AudioPlayer.Companion.get().addOnPlayEventListener(this);
......@@ -104,16 +102,6 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
sbProgress.setOnSeekBarChangeListener(this);
}
/**
* 沉浸式状态栏
*/
void initSystemBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
int top = ScreenUtils.INSTANCE.getStatusBarHeight(getActivity());
llContent.setPadding(0, top, 0, 0);
}
}
void initPlayMode() {
int mode = AudioPlayer.Companion.get().getPlayMode().value();
ivMode.setImageLevel(mode);
......@@ -138,14 +126,17 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
* 更新播放进度
*/
@Override
public void onPublish(int progress) {
public void onPublish(int percent,long currentPosition) {
if (!isDraggingProgress) {
sbProgress.setProgress(progress);
sbProgress.setProgress((int) currentPosition);
}
}
@Override
public void onBufferingUpdate(int percent) {
if(percent==0) {
return;
}
sbProgress.setSecondaryProgress(sbProgress.getMax() * 100 / percent);
}
......@@ -193,7 +184,7 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
isDraggingProgress = false;
if (AudioPlayer.Companion.get().isPlaying() || AudioPlayer.Companion.get().isPausing()) {
int progress = seekBar.getProgress();
AudioPlayer.Companion.get().seekTo(progress);
AudioPlayer.Companion.get().seekTo(-1, progress);
} else {
seekBar.setProgress(0);
}
......@@ -209,10 +200,10 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
tvArtist.setText(music.getArtist());
sbProgress.setProgress((int) AudioPlayer.Companion.get().getAudioPosition());
sbProgress.setSecondaryProgress(0);
sbProgress.setMax((int) music.getDuration());
mLastProgress = 0;
tvCurrentTime.setText(R.string.play_time_start);
tvTotalTime.setText(formatTime(music.getDuration()));
setCoverAndBg(music);
if (AudioPlayer.Companion.get().isPlaying() || AudioPlayer.Companion.get().isPreparing()) {
ivPlay.setSelected(true);
......@@ -259,9 +250,8 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
}
void setCoverAndBg(Music music) {
CoverLoader.Companion.get().loadRound(music, bitmap -> ivCover.setImageBitmap(bitmap));
CoverLoader.Companion.get().loadBlur(music, bitmap -> ivPlayingBg.setImageBitmap(bitmap));
ImageUtils.INSTANCE.loadRound(Objects.requireNonNull(music.getCoverPath()), bitmap -> ivCover.setImageBitmap(bitmap));
ImageUtils.INSTANCE.loadBlur(music.getCoverPath(), bitmap -> ivPlayingBg.setImageBitmap(bitmap));
}
String formatTime(long time) {
......@@ -281,4 +271,10 @@ public class PlayFragment extends Fragment implements View.OnClickListener,
AudioPlayer.Companion.get().removeOnPlayEventListener(this);
super.onDestroy();
}
@Override
public void onPrepared(long duration) {
sbProgress.setMax((int) duration);
tvTotalTime.setText(formatTime(duration));
}
}
......@@ -132,7 +132,7 @@ ext {
"systembartint" : "com.readystatesoftware.systembartint:systembartint:1.0.3",
"cube" : 'in.srain.cube:cube-sdk:1.0.44@aar',
"support-multidex" : 'com.android.support:multidex:1.0.2',
"ydl-ijkplayer-jjdxm" : 'com.ydl:jjdxm-ijkplayer:0.0.1',
"ydl-ijkplayer-jjdxm" : 'com.ydl:jjdxm-ijkplayer:0.0.3',
"robust" : 'com.meituan.robust:robust:0.4.87',
"walle" : 'com.meituan.android.walle:library:1.1.5',
"jpush" : 'cn.jiguang.sdk:jpush:3.2.0',
......
......@@ -50,6 +50,10 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
kapt 'com.alibaba:arouter-compiler:1.2.2'
api(rootProject.ext.dependencies["ydl-ijkplayer-jjdxm"]) {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
api rootProject.ext.dependencies["ydl-user-router"]
if (rootProject.ext.dev_mode){
//开发时使用
......
......@@ -3,16 +3,19 @@ package com.ydl.media.music
import android.content.Context
import android.content.IntentFilter
import android.media.AudioManager
import android.media.MediaPlayer
import android.os.Handler
import android.os.Looper
import com.tencent.bugly.Bugly.applicationContext
import com.ydl.media.music.enums.PlayModeEnum
import com.ydl.media.music.manager.AudioFocusManager
import com.ydl.media.music.manager.MediaSessionManager
import com.ydl.media.music.manager.NotifyManager
import com.ydl.media.music.model.Music
import com.ydl.media.music.receiver.NoisyAudioStreamReceiver
import com.ydl.media.music.utils.PlayProgressUtil
import com.yidianling.common.tools.ToastUtil
import tv.danmaku.ijk.media.player.IMediaPlayer
import tv.danmaku.ijk.media.player.IjkMediaPlayer
import java.io.IOException
import java.util.*
import kotlin.collections.ArrayList
......@@ -26,7 +29,7 @@ class AudioPlayer private constructor() {
private var context: Context? = null
private var audioFocusManager: AudioFocusManager? = null
var mediaPlayer: MediaPlayer? = null
var mediaPlayer: IMediaPlayer? = null
private var handler: Handler? = null
private var noisyReceiver: NoisyAudioStreamReceiver? = null
private var noisyFilter: IntentFilter? = null
......@@ -37,8 +40,15 @@ class AudioPlayer private constructor() {
private val mPublishRunnable = object : Runnable {
override fun run() {
if (isPlaying) {
val current = (mediaPlayer!!.currentPosition * 1.0).toFloat()
val du = mediaPlayer!!.duration.toFloat()
var percent = (current * 100 / du).toInt()
//保存进度
if (autoSaveProgress) {
PlayProgressUtil.saveProgress(context, playMusic!!.path,( if (percent == 99 || percent == 100) 0 else current.toInt()))
}
for (listener in listeners) {
listener.onPublish(mediaPlayer!!.currentPosition)
listener.onPublish(percent,current.toLong())
}
}
handler!!.postDelayed(this, TIME_UPDATE)
......@@ -79,11 +89,16 @@ class AudioPlayer private constructor() {
}
return field
}
/**
* 设置播放模式
* 默认为列表循环
*/
var playMode = PlayModeEnum.LIST_LOOP
/**
* 是否自动保存播放进度
*/
var autoSaveProgress = false
private object SingletonHolder {
val instance = AudioPlayer()
......@@ -95,28 +110,47 @@ class AudioPlayer private constructor() {
fun init(context: Context) {
this.context = context.applicationContext
audioFocusManager = AudioFocusManager(context)
mediaPlayer = MediaPlayer()
handler = Handler(Looper.getMainLooper())
noisyReceiver = NoisyAudioStreamReceiver()
noisyFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
mediaPlayer!!.setOnCompletionListener {
if (playMode == PlayModeEnum.SINGLE) {
stopPlayer()
playMode = PlayModeEnum.LIST_LOOP
return@setOnCompletionListener
}
next()
}
mediaPlayer = IjkMediaPlayer()
mediaPlayer!!.setOnPreparedListener { mp ->
if (isPreparing) {
if (autoSaveProgress) {
//自动播放
var time = PlayProgressUtil.getProgress(context, playMusic!!.path)
if (time > 0) {
startPlayer()
seekTo(position = time.toLong())
} else {
startPlayer()
}
}else{
startPlayer()
}
}
for (listener in listeners) {
listener.onPrepared(mediaPlayer!!.duration)
}
}
mediaPlayer!!.setOnBufferingUpdateListener { mp, percent ->
mediaPlayer!!.setOnBufferingUpdateListener{ mp, percent ->
for (listener in listeners) {
listener.onBufferingUpdate(percent)
}
}
mediaPlayer!!.setOnCompletionListener{
if (autoSaveProgress) {
PlayProgressUtil.saveProgress(applicationContext, playMusic!!.path, 0)
}
if (playMode == PlayModeEnum.SINGLE) {
stopPlayer()
playMode = PlayModeEnum.LIST_LOOP
return@setOnCompletionListener
}
next()
}
handler = Handler(Looper.getMainLooper())
noisyReceiver = NoisyAudioStreamReceiver()
noisyFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
}
/**
......@@ -144,6 +178,8 @@ class AudioPlayer private constructor() {
}
musicList.clear()
musicList.addAll(music)
playMode = PlayModeEnum.LIST_LOOP
// autoSaveProgress = false
}
/**
......@@ -153,6 +189,7 @@ class AudioPlayer private constructor() {
musicList.clear()
musicList.add(music)
playMode = PlayModeEnum.SINGLE
autoSaveProgress = false
play(0)
}
......@@ -176,7 +213,7 @@ class AudioPlayer private constructor() {
}
/**
* 播放指定索引的音乐
* 加载指定索引的音乐
*/
fun play(position: Int) {
var position = position
......@@ -191,11 +228,12 @@ class AudioPlayer private constructor() {
}
playPosition = position
val music = playMusic
try {
mediaPlayer!!.reset()
mediaPlayer!!.setDataSource(music!!.path)
mediaPlayer!!.dataSource = music!!.path
mediaPlayer!!.prepareAsync()
state = STATE_PREPARING
for (listener in listeners) {
......@@ -249,7 +287,7 @@ class AudioPlayer private constructor() {
}
/**
*开启播放器
*开始播放
*/
fun startPlayer() {
if (!isPreparing && !isPausing) {
......@@ -314,12 +352,19 @@ class AudioPlayer private constructor() {
if (musicList.isEmpty()) {
return
}
when (playMode) {
PlayModeEnum.SHUFFLE -> play(Random().nextInt(musicList.size))
PlayModeEnum.SINGLE_LOOP -> play(playPosition)
PlayModeEnum.LIST_LOOP -> play(playPosition + 1)
else -> play(playPosition + 1)
var playPosition = when (playMode) {
PlayModeEnum.SHUFFLE -> Random().nextInt(musicList.size)
PlayModeEnum.SINGLE_LOOP -> playPosition
PlayModeEnum.LIST_LOOP -> playPosition + 1
else -> playPosition + 1
}
play(playPosition);
}
/**
......@@ -339,15 +384,32 @@ class AudioPlayer private constructor() {
/**
* 跳转到指定的时间位置
*
* @param msec 时间
* @param percent 百分比
* @param position 时间点
*/
fun seekTo(msec: Int) {
fun seekTo(percent: Int = -1 , position:Long = -1) {
if (isPlaying || isPausing) {
mediaPlayer!!.seekTo(msec)
var currentPosition = 0L
var currentPercent = 0
if (position != (-1).toLong()){
val current = (currentPosition * 1.0).toFloat()
val du = mediaPlayer!!.duration.toFloat()
currentPercent = (current * 100 / du).toInt()
currentPosition = position
} else {
currentPosition = percent * mediaPlayer!!.duration / 100
}
mediaPlayer!!.seekTo(currentPosition)
MediaSessionManager.get().updatePlaybackState()
if (autoSaveProgress) {
PlayProgressUtil.saveProgress(context, musicList[playPosition].coverPath, currentPosition.toInt())
}
for (listener in listeners) {
listener.onPublish(msec)
listener.onPublish(currentPercent,currentPosition)
}
}
}
......@@ -359,6 +421,7 @@ class AudioPlayer private constructor() {
return musicList
}
companion object {
private val STATE_IDLE = 0
private val STATE_PREPARING = 1
......
......@@ -26,11 +26,20 @@ interface OnPlayerEventListener {
/**
* 更新进度
* percent : 播放百分比
* currentPosition:当前播放位置
*/
fun onPublish(progress: Int)
fun onPublish(percent: Int,currentPosition: Long)
/**
* 缓冲百分比
*/
fun onBufferingUpdate(percent: Int)
/**
* 准备完成
*
* duration:时长
*/
fun onPrepared(duration:Long)
}
......@@ -8,7 +8,7 @@ import android.support.v4.media.session.PlaybackStateCompat
import com.ydl.media.music.AudioPlayer
import com.ydl.media.music.PlayService
import com.ydl.media.music.model.Music
import com.ydl.media.music.utils.CoverLoader
import com.ydl.media.music.utils.ImageUtils
/**
......@@ -79,8 +79,8 @@ class MediaSessionManager private constructor() {
return
}
CoverLoader.get().loadThumb(music,object :CoverLoader.OnCoverLoadListenre{
override fun onCpmplete(bitmap: Bitmap) {
ImageUtils.loadThumb(music.coverPath!!,object :ImageUtils.OnCoverLoadListenre{
override fun onComplete(bitmap: Bitmap) {
val metaData = MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, music.title)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, music.artist)
......
package com.ydl.media.music.utils
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.Drawable
import android.text.TextUtils
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import com.ydl.media.music.model.Music
/**
* Created by haorui on 2019-10-27 .
* Des: 专辑封面图片加载器
*/
class CoverLoader private constructor() {
private var context: Context? = null
private var roundLength = 0
private enum class Type {
THUMB,
ROUND,
BLUR
}
private object SingletonHolder {
val instance = CoverLoader()
}
fun init(context: Context) {
this.context = context.applicationContext
this.roundLength = ScreenUtils.getScreenWidth(context) / 2
}
fun setRoundLength(roundLength: Int) {
if (this.roundLength != roundLength) {
this.roundLength = roundLength
}
}
fun loadThumb(music: Music,onCoverLoadListener: OnCoverLoadListenre) {
loadCover(music, Type.THUMB,onCoverLoadListener)
}
fun loadRound(music: Music,onCoverLoadListener: OnCoverLoadListenre){
loadCover(music, Type.ROUND,onCoverLoadListener)
}
fun loadBlur(music: Music,onCoverLoadListener: OnCoverLoadListenre){
loadCover(music, Type.BLUR,onCoverLoadListener)
}
private fun loadCover(music: Music?, type: Type, onCoverLoadListener: OnCoverLoadListenre){
val key = getKey(music)
if (TextUtils.isEmpty(key)) {
onCoverLoadListener.onCpmplete(getDefaultCover(type)!!)
return
}
loadCoverByType(music!!, type,onCoverLoadListener)
}
private fun getKey(music: Music?): String? {
if (music == null) {
return null
}
return if (!TextUtils.isEmpty(music.coverPath)) {
music.coverPath
} else {
null
}
}
private fun getDefaultCover(type: Type): Bitmap? {
when (type) {
CoverLoader.Type.ROUND -> {
var bitmap: Bitmap? = BitmapFactory.decodeResource(
context!!.resources,
com.ydl.media.R.drawable.play_page_default_cover
)
bitmap = ImageUtils.resizeImage(bitmap, roundLength, roundLength)
return bitmap
}
CoverLoader.Type.BLUR -> return BitmapFactory.decodeResource(
context!!.resources,
com.ydl.media.R.drawable.play_page_default_bg
)
else -> return BitmapFactory.decodeResource(
context!!.resources,
com.ydl.media.R.drawable.default_cover
)
}
}
@SuppressLint("CheckResult")
private fun loadCoverByType(music: Music, type: Type, onCoverLoadListener: OnCoverLoadListenre) {
val disallowHardwareConfig =
RequestOptions().format(DecodeFormat.PREFER_RGB_565).disallowHardwareConfig()
Glide.with(context).asBitmap().apply(disallowHardwareConfig).load(music.coverPath).into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap?, transition: Transition<in Bitmap>?) {
when (type) {
CoverLoader.Type.ROUND -> {
var bitmap = ImageUtils.resizeImage(resource, roundLength, roundLength)
onCoverLoadListener.onCpmplete(ImageUtils.createCircleImage(bitmap)!!)
}
CoverLoader.Type.BLUR -> {
onCoverLoadListener.onCpmplete(ImageUtils.blur(resource)!!)
}
}
}
override fun onLoadFailed(errorDrawable: Drawable?) {
loadCover(null, type,onCoverLoadListener)
}
})
}
companion object {
val THUMBNAIL_MAX_LENGTH = 500
private val KEY_NULL = "null"
fun get(): CoverLoader {
return SingletonHolder.instance
}
}
interface OnCoverLoadListenre {
fun onCpmplete(bitmap: Bitmap)
}
}
package com.ydl.media.music.utils
import android.graphics.*
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import com.ydl.ydlcommon.base.BaseApp
/**
*
* Created by wcy on 2015/11/29.
*/
/**
* Created by haorui on 2019-10-27 .
* Des:
*/
......@@ -344,4 +347,38 @@ object ImageUtils {
return target
}
fun loadThumb(url: String,onCoverLoadListener: OnCoverLoadListenre) {
Glide.with(BaseApp.getApp()).asBitmap()
.load(url).into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap?, transition: Transition<in Bitmap>?) {
onCoverLoadListener.onComplete(resource!!)
}
})
}
fun loadRound(url: String,onCoverLoadListener: OnCoverLoadListenre){
Glide.with(BaseApp.getApp()).asBitmap()
.apply(
RequestOptions.bitmapTransform(
CircleCrop()
)).load(url).into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap?, transition: Transition<in Bitmap>?) {
onCoverLoadListener.onComplete(resource!!)
}
})
}
fun loadBlur(url: String,onCoverLoadListener: OnCoverLoadListenre){
val disallowHardwareConfig =
RequestOptions().format(DecodeFormat.PREFER_RGB_565).disallowHardwareConfig()
Glide.with(BaseApp.getApp()).asBitmap().apply(disallowHardwareConfig).load(url).into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap?, transition: Transition<in Bitmap>?) {
onCoverLoadListener.onComplete(ImageUtils.blur(resource)!!)
}
})
}
interface OnCoverLoadListenre {
fun onComplete(bitmap: Bitmap)
}
}
package com.ydl.media.music.utils
import android.content.Context
import android.text.TextUtils
/**
* 工具类
* Created by hgw on 2018/3/31.
*/
object PlayProgressUtil {
/**
* 保存播放进度
*/
fun saveProgress(context: Context?,url : String?,progress : Int){
if (TextUtils.isEmpty(url))return
var shared = context?.getSharedPreferences("AUDIO_PLAYER_MUSIC_PROGRESS",Context.MODE_PRIVATE)
var edit = shared?.edit()
edit?.putInt(url,progress)
edit?.commit()
}
/**
* 获取进度
*/
fun getProgress(context: Context?,url : String?):Int{
if (TextUtils.isEmpty(url))return 0
var shared = context?.getSharedPreferences("AUDIO_PLAYER_MUSIC_PROGRESS",Context.MODE_PRIVATE)
return shared?.getInt(url,0)?:0
}
}
\ No newline at end of file
package com.ydl.media.music.utils
import android.annotation.SuppressLint
import android.content.Context
import android.view.WindowManager
/**
* Created by haorui on 2019-10-27 .
* Des:
*/
@SuppressLint("StaticFieldLeak")
object ScreenUtils {
fun getScreenWidth(context: Context): Int{
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
return wm.defaultDisplay.width
}
/**
* 获取状态栏高度
*/
fun getStatusBarHeight(context:Context): Int{
var result = 0
val resourceId =
context!!.resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = context!!.resources.getDimensionPixelSize(resourceId)
}
return result
}
fun dp2px(context: Context,dpValue: Float): Int {
val scale = context!!.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}
fun px2dp(context: Context,pxValue: Float): Int {
val scale = context!!.resources.displayMetrics.density
return (pxValue / scale + 0.5f).toInt()
}
fun sp2px(context: Context,spValue: Float): Int {
val fontScale = context!!.resources.displayMetrics.scaledDensity
return (spValue * fontScale + 0.5f).toInt()
}
}
......@@ -121,9 +121,7 @@ dependencies {
api rootProject.ext.dependencies["flowlayout"]
api rootProject.ext.dependencies["androidanimations"]
api(rootProject.ext.dependencies["ydl-ijkplayer-jjdxm"]) {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
api rootProject.ext.dependencies["exoplayer"]
//====================Tools====================
api rootProject.ext.dependencies["robust"]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment