Parcourir la source

完善视频播放器内核库

yangchong il y a 3 ans
Parent
commit
593e79f35e

+ 1 - 0
Demo/build.gradle

@@ -46,6 +46,7 @@ dependencies {
     implementation 'androidx.recyclerview:recyclerview:1.1.0'
     implementation 'com.google.android.material:material:1.2.1'
     implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
+    implementation 'pub.devrel:easypermissions:3.0.0'
 
     //其他库
     implementation 'com.github.bumptech.glide:glide:4.9.0'                 //谷歌图片加载库

+ 8 - 1
Demo/src/main/AndroidManifest.xml

@@ -8,6 +8,12 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 
+<!--    音频-->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+
+
+
     <application
         android:name="com.yc.ycvideoplayer.BaseApplication"
         android:allowBackup="true"
@@ -18,7 +24,7 @@
         android:networkSecurityConfig="@xml/network_security_config"
         android:theme="@style/AppTheme"
         tools:ignore="GoogleAppIndexingWarning">
-        <activity android:name="com.yc.ycvideoplayer.MainActivity"
+        <activity android:name="com.yc.ycvideoplayer.SplashActivity"
             android:configChanges="orientation|keyboardHidden|screenSize"
             android:screenOrientation="portrait">
             <intent-filter>
@@ -27,6 +33,7 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name=".MainActivity"/>
         <activity android:name="com.yc.ycvideoplayer.video.activity.TypeActivity"/>
         <activity android:name="com.yc.ycvideoplayer.video.list.TestListActivity"
             android:configChanges="orientation|keyboardHidden|screenSize"

+ 3 - 0
Demo/src/main/java/com/yc/ycvideoplayer/BaseApplication.java

@@ -12,6 +12,7 @@ import com.yc.kernel.factory.PlayerFactory;
 import com.yc.kernel.utils.PlayerConstant;
 import com.yc.kernel.utils.PlayerFactoryUtils;
 
+import com.yc.music.tool.BaseAppHelper;
 import com.yc.video.config.VideoPlayerConfig;
 import com.yc.video.player.VideoViewManager;
 import com.yc.videosqllite.manager.CacheConfig;
@@ -82,6 +83,8 @@ public class BaseApplication extends Application {
                 //初始化一些耗时的操作
             }
         });
+
+        BaseAppHelper.get().setContext(this);
     }
 
     private void initVideoCache() {

+ 2 - 18
Demo/src/main/java/com/yc/ycvideoplayer/MainActivity.java

@@ -223,7 +223,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
      * 检测服务
      */
     private void startCheckService() {
-        if (BaseAppHelper.get().getPlayService() == null) {
+        if (BaseAppHelper.get().getMusicService() == null) {
             startService();
             mTv1.postDelayed(new Runnable() {
                 @Override
@@ -255,28 +255,12 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
     }
 
 
-    private class PlayServiceConnection implements ServiceConnection {
+    private static class PlayServiceConnection implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             VideoLogUtils.e("onServiceConnected"+name);
             final PlayAudioService playService = (PlayAudioService) ((PlayAudioService.PlayBinder) service).getService();
             BaseAppHelper.get().setPlayService(playService);
-            List<AudioBean> musicList = BaseAppHelper.get().getMusicList();
-            AudioBean audioBean1 = new AudioBean();
-            audioBean1.setPath("http://img.zhugexuetang.com/lleXB2SNF5UFp1LfNpPI0hsyQjNs");
-            audioBean1.setId("1");
-            audioBean1.setTitle("音频1");
-            musicList.add(audioBean1);
-            AudioBean audioBean2 = new AudioBean();
-            audioBean2.setPath("http://img.zhugexuetang.com/ljUa-X-oDbLHu7n9AhkuMLu2Yz3k");
-            audioBean2.setId("2");
-            audioBean2.setTitle("音频2");
-            musicList.add(audioBean2);
-            AudioBean audioBean3 = new AudioBean();
-            audioBean3.setPath("http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4");
-            audioBean3.setId("3");
-            audioBean3.setTitle("音频3");
-            musicList.add(audioBean3);
         }
 
         @Override

+ 123 - 0
Demo/src/main/java/com/yc/ycvideoplayer/SplashActivity.java

@@ -0,0 +1,123 @@
+package com.yc.ycvideoplayer;
+
+import android.Manifest;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.yc.video.tool.BaseToast;
+
+import java.util.List;
+
+import cn.ycbjie.ycstatusbarlib.bar.StateAppBar;
+import pub.devrel.easypermissions.AfterPermissionGranted;
+import pub.devrel.easypermissions.AppSettingsDialog;
+import pub.devrel.easypermissions.EasyPermissions;
+
+public class SplashActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_splash);
+        StateAppBar.translucentStatusBar(this, true);
+        initPermissions();
+    }
+
+
+    /**
+     * 这个地方一定先要初始化权限设置,因为对于音频播放,会扫描本地音乐,涉及权限。如果不允许,可能会导致崩溃。
+     * 但是这样体验可能不太好,如果不允许权限,则直接退出。
+     * 后来发现华为音乐,混沌大学,QQ音乐等,没有打开权限,都是直接退出的。
+     */
+    private void initPermissions() {
+        locationPermissionsTask();
+    }
+
+    private static final int RC_LOCATION_CONTACTS_PERM = 124;
+    private static final String[] LOCATION_AND_CONTACTS = {
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+            Manifest.permission.RECORD_AUDIO,
+    };
+
+    @AfterPermissionGranted(RC_LOCATION_CONTACTS_PERM)
+    public void locationPermissionsTask() {
+        //检查是否获取该权限
+        if (hasPermissions()) {
+            startActivity(new Intent(this,MainActivity.class));
+            finish();
+        } else {
+            //权限拒绝 申请权限
+            //第二个参数是被拒绝后再次申请该权限的解释
+            //第三个参数是请求码
+            //第四个参数是要申请的权限
+            EasyPermissions.requestPermissions(this,
+                    getString(R.string.easy_permissions), RC_LOCATION_CONTACTS_PERM, LOCATION_AND_CONTACTS);
+        }
+    }
+
+
+    /**
+     * 判断是否添加了权限
+     *
+     * @return true
+     */
+    private boolean hasPermissions() {
+        return EasyPermissions.hasPermissions(this, LOCATION_AND_CONTACTS);
+    }
+
+
+    /**
+     * 将结果转发到EasyPermissions
+     */
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        // 将结果转发到EasyPermissions
+        EasyPermissions.onRequestPermissionsResult(requestCode, permissions,
+                grantResults, this);
+    }
+
+
+    /**
+     * 某些权限已被授予
+     */
+    @Override
+    public void onPermissionsGranted(int requestCode, List<String> perms) {
+        //某些权限已被授予
+        Log.d("权限", "onPermissionsGranted:" + requestCode + ":" + perms.size());
+    }
+
+
+    /**
+     * 某些权限已被拒绝
+     */
+    @Override
+    public void onPermissionsDenied(int requestCode, List<String> perms) {
+        //某些权限已被拒绝
+        Log.d("权限", "onPermissionsDenied:" + requestCode + ":" + perms.size());
+        // (Optional) Check whether the user denied any permissions and checked "NEVER ASK AGAIN."
+        // This will display a dialog directing them to enable the permission in app settings.
+        if (EasyPermissions.somePermissionPermanentlyDenied(SplashActivity.this, perms)) {
+            Log.d("权限", "somePermissionPermanentlyDenied:" + perms.get(0));
+            AppSettingsDialog.Builder builder = new AppSettingsDialog.Builder(SplashActivity.this);
+            builder.setTitle("允许权限")
+                    .setRationale("没有该权限,此应用程序部分功能可能无法正常工作。打开应用设置界面以修改应用权限")
+                    .setPositiveButton("去设置")
+                    .setNegativeButton("取消")
+                    .setRequestCode(RC_LOCATION_CONTACTS_PERM)
+                    .build()
+                    .show();
+        }else {
+            BaseToast.showRoundRectToast("没有存储空间权限,无法扫描本地歌曲!");
+            finish();
+        }
+    }
+
+
+}

+ 244 - 0
Demo/src/main/java/com/yc/ycvideoplayer/music/CoverLoader.java

@@ -0,0 +1,244 @@
+package com.yc.ycvideoplayer.music;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.LruCache;
+import android.view.WindowManager;
+
+import com.yc.music.model.AudioBean;
+import com.yc.music.tool.BaseAppHelper;
+import com.yc.music.utils.ImageUtils;
+import com.yc.ycvideoplayer.R;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+/**
+ * 专辑封面图片加载器
+ */
+public class CoverLoader {
+
+    private static final String KEY_NULL = "null";
+
+    /**
+     * 封面缓存
+     * 使用LruCache作为缓冲集合
+     */
+    private final LruCache<String, Bitmap> mCoverCache;
+
+    private enum Type {
+        THUMBNAIL(""),
+        BLUR("#BLUR"),
+        ROUND("#ROUND");
+
+        private final String value;
+        Type(String value) {
+            this.value = value;
+        }
+    }
+
+    /**
+     * 使用单利模式获取对象
+     * @return              CoverLoader对象
+     */
+    public static CoverLoader getInstance() {
+        return SingletonHolder.instance;
+    }
+
+    private static class SingletonHolder {
+        static CoverLoader instance = new CoverLoader();
+    }
+
+    private CoverLoader() {
+        // 获取当前进程的可用内存(单位KB)
+        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
+        // 缓存大小为当前进程可用内存的1/8
+        int cacheSize = maxMemory / 8;
+        mCoverCache = new LruCache<String, Bitmap>(cacheSize) {
+            @Override
+            protected int sizeOf(String key, Bitmap bitmap) {
+                //API19
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+                    return bitmap.getAllocationByteCount() / 1024;
+                } else {
+                    return bitmap.getByteCount() / 1024;
+                }
+            }
+        };
+    }
+
+    /**
+     * 获取小图标
+     * @param music             music
+     * @return                  bitmap对象
+     */
+    public Bitmap loadThumbnail(AudioBean music) {
+        return loadCover(music, Type.THUMBNAIL);
+    }
+
+    /**
+     * 获取蒙层透明背景bitmap
+     * @param music             music
+     * @return                  bitmap对象
+     */
+    public Bitmap loadBlur(AudioBean music) {
+        return loadCover(music, Type.BLUR);
+    }
+
+    /**
+     * 获取蒙层透明背景bitmap
+     * @param music             music
+     * @return                  bitmap对象
+     */
+    public Bitmap loadRound(AudioBean music) {
+        return loadCover(music, Type.ROUND);
+    }
+
+
+    private Bitmap loadCover(AudioBean music, Type type) {
+        Bitmap bitmap;
+        String key = getKey(music, type);
+        if (TextUtils.isEmpty(key)) {
+            bitmap = mCoverCache.get(KEY_NULL.concat(type.value));
+            if (bitmap != null) {
+                return bitmap;
+            }
+            bitmap = getDefaultCover(type);
+            mCoverCache.put(KEY_NULL.concat(type.value), bitmap);
+            return bitmap;
+        }
+        bitmap = mCoverCache.get(key);
+        if (bitmap != null) {
+            return bitmap;
+        }
+        bitmap = loadCoverByType(music, type);
+        if (bitmap != null) {
+            mCoverCache.put(key, bitmap);
+            return bitmap;
+        }
+        return loadCover(null, type);
+    }
+
+
+    private String getKey(AudioBean music, Type type) {
+        if (music == null) {
+            return null;
+        }
+        if ( music.getAlbumId() > 0) {
+            return String.valueOf(music.getAlbumId()).concat(type.value);
+        } else {
+            return null;
+        }
+    }
+
+
+    /**
+     * 获取默认的bitmap视图
+     * @param type          类型
+     * @return              bitmap对象
+     */
+    private Bitmap getDefaultCover(Type type) {
+        Context context = BaseAppHelper.get().getContext();
+        switch (type) {
+            case ROUND:
+                Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),
+                        R.drawable.default_cover);
+                bitmap = ImageUtils.resizeImage(bitmap,
+                        getScreenWidth() / 2, getScreenWidth() / 2);
+                return bitmap;
+            default:
+                return BitmapFactory.decodeResource(context.getResources(), R.drawable.default_cover);
+        }
+    }
+
+
+    private Bitmap loadCoverByType(AudioBean music, Type type) {
+        Bitmap bitmap = loadCoverFromMediaStore(music.getAlbumId());
+        switch (type) {
+            case BLUR:
+                return ImageUtils.blur(bitmap);
+            case ROUND:
+                bitmap = ImageUtils.resizeImage(bitmap,
+                        getScreenWidth() / 2, getScreenWidth() / 2);
+                return ImageUtils.createCircleImage(bitmap);
+            default:
+                return bitmap;
+        }
+    }
+
+
+    /**
+     * 从媒体库加载封面<br>
+     * 本地音乐
+     */
+    private Bitmap loadCoverFromMediaStore(long albumId) {
+        Context context = BaseAppHelper.get().getContext();
+        ContentResolver resolver = context.getContentResolver();
+        Uri uri = getMediaStoreAlbumCoverUri(albumId);
+        InputStream is;
+        try {
+            is = resolver.openInputStream(uri);
+        } catch (FileNotFoundException ignored) {
+            return null;
+        }
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.RGB_565;
+        return BitmapFactory.decodeStream(is, null, options);
+    }
+
+    /**
+     * 从下载的图片加载封面<br>
+     * 网络音乐
+     */
+    private Bitmap loadCoverFromFile(String path) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.RGB_565;
+        return BitmapFactory.decodeFile(path, options);
+    }
+
+    private Uri getMediaStoreAlbumCoverUri(long albumId) {
+        Uri artworkUri = Uri.parse("content://media/external/audio/albumart");
+        return ContentUris.withAppendedId(artworkUri, albumId);
+    }
+
+
+    private int getScreenWidth() {
+        Context context = BaseAppHelper.get().getContext();
+        WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+        if (wm == null) {
+            return context.getResources().getDisplayMetrics().widthPixels;
+        } else {
+            Point point = new Point();
+            if (Build.VERSION.SDK_INT >= 17) {
+                wm.getDefaultDisplay().getRealSize(point);
+            } else {
+                wm.getDefaultDisplay().getSize(point);
+            }
+
+            return point.x;
+        }
+    }
+
+    private int getScreenHeight() {
+        Context context = BaseAppHelper.get().getContext();
+        WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+        if (wm == null) {
+            return context.getResources().getDisplayMetrics().heightPixels;
+        } else {
+            Point point = new Point();
+            if (Build.VERSION.SDK_INT >= 17) {
+                wm.getDefaultDisplay().getRealSize(point);
+            } else {
+                wm.getDefaultDisplay().getSize(point);
+            }
+
+            return point.y;
+        }
+    }
+}

+ 152 - 0
Demo/src/main/java/com/yc/ycvideoplayer/music/FileMusicScanManager.java

@@ -0,0 +1,152 @@
+package com.yc.ycvideoplayer.music;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import com.yc.music.model.AudioBean;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * <pre>
+ *     author: yangchong
+ *     blog  : https://github.com/yangchong211
+ *     time  : 2018/01/22
+ *     desc  : 本地音乐扫描工具类
+ *     revise: 参考链接:https://www.jianshu.com/p/498c9d06c193
+ *                      https://blog.csdn.net/chay_chan/article/details/76984665
+ * </pre>
+ */
+
+public class FileMusicScanManager {
+
+
+    private static FileMusicScanManager mInstance;
+    private static final Object mLock = new Object();
+
+    public static FileMusicScanManager getInstance(){
+        if (mInstance == null){
+            synchronized (mLock){
+                if (mInstance == null){
+                    mInstance = new FileMusicScanManager();
+                }
+            }
+        }
+        return mInstance;
+    }
+
+    /**----------------------------------扫描歌曲------------------------------------------------**/
+
+    private static final String SELECTION = MediaStore.Audio.AudioColumns.SIZE + " >= ? AND " +
+            MediaStore.Audio.AudioColumns.DURATION + " >= ?";
+
+
+    /**
+     * 扫描歌曲
+     */
+    @NonNull
+    public List<AudioBean> scanMusic(Context context) {
+        List<AudioBean> musicList = new ArrayList<>();
+        String mFilterSize = "0";
+        String mFilterTime = "0";
+
+        long filterSize = parseLong(mFilterSize) * 1024;
+        long filterTime = parseLong(mFilterTime) * 1000;
+
+        Cursor cursor = context.getContentResolver().query(
+                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                new String[]{
+                        BaseColumns._ID,
+                        MediaStore.Audio.AudioColumns.IS_MUSIC,
+                        MediaStore.Audio.AudioColumns.TITLE,
+                        MediaStore.Audio.AudioColumns.ARTIST,
+                        MediaStore.Audio.AudioColumns.ALBUM,
+                        MediaStore.Audio.AudioColumns.ALBUM_ID,
+                        MediaStore.Audio.AudioColumns.DATA,
+                        MediaStore.Audio.AudioColumns.DISPLAY_NAME,
+                        MediaStore.Audio.AudioColumns.SIZE,
+                        MediaStore.Audio.AudioColumns.DURATION
+                },
+                SELECTION,
+                new String[]{String.valueOf(filterSize), String.valueOf(filterTime)},
+                MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+
+        if (cursor == null) {
+            return musicList;
+        }
+
+        int i = 0;
+        while (cursor.moveToNext()) {
+            // 是否为音乐,魅族手机上始终为0
+            int isMusic = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.IS_MUSIC));
+            if (!isFly() && isMusic == 0) {
+                continue;
+            }
+
+            long id = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
+            String title = cursor.getString((cursor.getColumnIndex(MediaStore.Audio.AudioColumns.TITLE)));
+            String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.ARTIST));
+            String album = cursor.getString((cursor.getColumnIndex(MediaStore.Audio.AudioColumns.ALBUM)));
+            long albumId = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.ALBUM_ID));
+            long duration = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
+            String path = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.DATA));
+            String fileName = cursor.getString((cursor.getColumnIndex(MediaStore.Audio.AudioColumns.DISPLAY_NAME)));
+            long fileSize = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE));
+
+            AudioBean music = new AudioBean();
+            music.setId(String.valueOf(id));
+            music.setTitle(title);
+            music.setArtist(artist);
+            music.setAlbum(album);
+            music.setAlbumId(albumId);
+            music.setDuration(duration);
+            music.setPath(path);
+            if (++i <= 20) {
+                // 只加载前20首的缩略图
+                CoverLoader.getInstance().loadThumbnail(music);
+            }
+            musicList.add(music);
+        }
+        cursor.close();
+        return musicList;
+    }
+
+
+    private long parseLong(String s) {
+        try {
+            return Long.parseLong(s);
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+    }
+
+
+    private boolean isFly() {
+        String flyFlag = getSystemProperty("ro.build.display.id");
+        return !TextUtils.isEmpty(flyFlag) && flyFlag.toLowerCase().contains("fly");
+    }
+
+
+    private String getSystemProperty(String key) {
+        try {
+            @SuppressLint("PrivateApi")
+            Class<?> classType = Class.forName("android.os.SystemProperties");
+            Method getMethod = classType.getDeclaredMethod("get", String.class);
+            return (String) getMethod.invoke(classType, key);
+        } catch (Throwable th) {
+            th.printStackTrace();
+        }
+        return null;
+    }
+
+
+}

+ 139 - 0
Demo/src/main/java/com/yc/ycvideoplayer/music/MusicAdapter.java

@@ -0,0 +1,139 @@
+package com.yc.ycvideoplayer.music;
+
+import android.graphics.Bitmap;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.Glide;
+import com.yc.music.model.AudioBean;
+import com.yc.music.service.PlayAudioService;
+import com.yc.video.config.VideoInfoBean;
+import com.yc.video.ui.view.CustomPrepareView;
+import com.yc.ycvideoplayer.R;
+import com.yc.ycvideoplayer.video.list.OnItemChildClickListener;
+import com.yc.ycvideoplayer.video.list.OnItemClickListener;
+
+import java.util.List;
+
+public class MusicAdapter extends RecyclerView.Adapter<MusicAdapter.VideoHolder> {
+
+    private List<AudioBean> videos;
+    private OnItemClickListener mOnItemClickListener;
+
+    /**
+     * 正在播放音乐的索引位置
+     */
+    private int mPlayingPosition;
+
+    public MusicAdapter(List<AudioBean> videos) {
+        this.videos = videos;
+    }
+
+    @Override
+    @NonNull
+    public VideoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        View itemView = LayoutInflater.from(parent.getContext()).inflate(
+                R.layout.item_local_music, parent, false);
+        return new VideoHolder(itemView);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull VideoHolder holder, int position) {
+
+        AudioBean data = videos.get(position);
+        Bitmap cover = CoverLoader.getInstance().loadThumbnail(data);
+        holder.ivCover.setImageBitmap(cover);
+        holder.tvTitle.setText(data.getTitle());
+        String artist = getArtistAndAlbum(data.getArtist(), data.getAlbum());
+        holder.tvArtist.setText(artist);
+        holder.ivMore.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+            }
+        });
+        if (position == mPlayingPosition) {
+            holder.vPlaying.setVisibility(View.VISIBLE);
+        } else {
+            holder.vPlaying.setVisibility(View.INVISIBLE);
+        }
+        holder.mPosition = position;
+    }
+
+    @Override
+    public int getItemCount() {
+        return videos==null ? 0 : videos.size();
+    }
+
+    public void addData(List<AudioBean> videoList) {
+        int size = videos.size();
+        videos.addAll(videoList);
+        //使用此方法添加数据,使用notifyDataSetChanged会导致正在播放的视频中断
+        notifyItemRangeChanged(size, videos.size());
+    }
+
+
+    /**
+     * 当播放位置发生了变化,那么就可以更新播放位置视图
+     */
+    public void updatePlayingPosition(int playingPosition) {
+        mPlayingPosition = playingPosition;
+    }
+
+    public class VideoHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+
+        public int mPosition;
+        View vPlaying;
+        ImageView ivCover;
+        TextView tvTitle;
+        TextView tvArtist;
+        ImageView ivMore;
+        View vDivider;
+
+        VideoHolder(View itemView) {
+            super(itemView);
+            vPlaying = itemView.findViewById(R.id.v_playing);
+            ivCover = itemView.findViewById(R.id.iv_cover);
+            tvTitle = itemView.findViewById(R.id.tv_title);
+            tvArtist = itemView.findViewById(R.id.tv_artist);
+            ivMore = itemView.findViewById(R.id.iv_more);
+            vDivider = itemView.findViewById(R.id.v_divider);
+            if (mOnItemClickListener != null) {
+                itemView.setOnClickListener(this);
+            }
+            //通过tag将ViewHolder和itemView绑定
+            itemView.setTag(this);
+        }
+
+        @Override
+        public void onClick(View v) {
+            if (mOnItemClickListener != null) {
+                mOnItemClickListener.onItemClick(mPosition);
+            }
+        }
+    }
+
+    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
+        mOnItemClickListener = onItemClickListener;
+    }
+
+    public String getArtistAndAlbum(String artist, String album) {
+        if (TextUtils.isEmpty(artist) && TextUtils.isEmpty(album)) {
+            return "";
+        } else if (!TextUtils.isEmpty(artist) && TextUtils.isEmpty(album)) {
+            return artist;
+        } else if (TextUtils.isEmpty(artist) && !TextUtils.isEmpty(album)) {
+            return album;
+        } else {
+            return artist + " - " + album;
+        }
+    }
+}

+ 121 - 48
Demo/src/main/java/com/yc/ycvideoplayer/music/MusicPlayerActivity.java

@@ -1,5 +1,7 @@
 package com.yc.ycvideoplayer.music;
 
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -11,22 +13,26 @@ import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.appcompat.widget.Toolbar;
 import androidx.fragment.app.FragmentTransaction;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.yc.music.inter.OnPlayerEventListener;
 import com.yc.music.model.AudioBean;
+import com.yc.music.service.PlayAudioService;
 import com.yc.music.tool.BaseAppHelper;
+import com.yc.videotool.VideoLogUtils;
 import com.yc.ycvideoplayer.R;
+import com.yc.ycvideoplayer.video.list.OnItemChildClickListener;
+import com.yc.ycvideoplayer.video.list.OnItemClickListener;
+import com.yc.ycvideoplayer.video.list.VideoRecyclerViewAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class MusicPlayerActivity extends AppCompatActivity implements View.OnClickListener {
 
     private Toolbar mToolbar;
-    private TextView mTv1;
-    private TextView mTv2;
-    private TextView mTv3;
-    private TextView mTvStart;
-    private TextView mTvStop;
-    private TextView mTvNext;
-    private TextView mTvPre;
+    private RecyclerView recyclerView;
     private FrameLayout mFlPlayBar;
     private ImageView mIvPlayBarCover;
     private TextView mTvPlayBarTitle;
@@ -37,6 +43,7 @@ public class MusicPlayerActivity extends AppCompatActivity implements View.OnCli
     private ProgressBar mPbPlayBar;
     private boolean isPlayFragmentShow = false;
     private PlayMusicFragment mPlayFragment;
+    private MusicAdapter musicAdapter;
 
     @Override
     public void onBackPressed() {
@@ -53,24 +60,15 @@ public class MusicPlayerActivity extends AppCompatActivity implements View.OnCli
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_music_player);
         initFindViewById();
+        initRecyclerView();
         initListener();
-        mTv1.postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                initPlayServiceListener();
-            }
-        },1000);
+        initData();
+        initPlayServiceListener();
     }
 
     private void initFindViewById() {
+        recyclerView = findViewById(R.id.recyclerView);
         mToolbar = findViewById(R.id.toolbar);
-        mTv1 = findViewById(R.id.tv_1);
-        mTv2 = findViewById(R.id.tv_2);
-        mTv3 = findViewById(R.id.tv_3);
-        mTvStart = findViewById(R.id.tv_start);
-        mTvStop = findViewById(R.id.tv_stop);
-        mTvNext = findViewById(R.id.tv_next);
-        mTvPre = findViewById(R.id.tv_pre);
         mFlPlayBar = findViewById(R.id.fl_play_bar);
         mIvPlayBarCover = findViewById(R.id.iv_play_bar_cover);
         mTvPlayBarTitle = findViewById(R.id.tv_play_bar_title);
@@ -79,48 +77,98 @@ public class MusicPlayerActivity extends AppCompatActivity implements View.OnCli
         mIvPlayBarPlay = findViewById(R.id.iv_play_bar_play);
         mIvPlayBarNext = findViewById(R.id.iv_play_bar_next);
         mPbPlayBar = findViewById(R.id.pb_play_bar);
+    }
 
+    private void initRecyclerView() {
+        LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(this);
+        recyclerView.setLayoutManager(mLinearLayoutManager);
+        List<AudioBean> musicList = BaseAppHelper.get().getMusicList();
+        musicAdapter = new MusicAdapter(musicList);
+        musicAdapter.setOnItemClickListener(new OnItemClickListener() {
+            @Override
+            public void onItemClick(int position) {
+                List<AudioBean> musicList = BaseAppHelper.get().getMusicList();
+                if(musicList!=null && musicList.size()>0 && musicList.size()>position && position>=0){
+                    BaseAppHelper.get().getMusicService().play(position);
+                    musicAdapter.updatePlayingPosition(position);
+                    musicAdapter.notifyDataSetChanged();
+                }
+            }
+        });
+        recyclerView.setAdapter(musicAdapter);
     }
 
     private void initListener() {
-        mTv1.setOnClickListener(this);
-        mTv2.setOnClickListener(this);
-        mTv3.setOnClickListener(this);
-        mTvStart.setOnClickListener(this);
-        mTvStop.setOnClickListener(this);
-        mTvNext.setOnClickListener(this);
-        mTvPre.setOnClickListener(this);
         mIvPlayBarPlay.setOnClickListener(this);
         mFlPlayBar.setOnClickListener(this);
+        mIvPlayBarNext.setOnClickListener(this);
+    }
+
+    private void initData() {
+        if (BaseAppHelper.get().getMusicList()==null || BaseAppHelper.get().getMusicList().size()==0){
+            new AsyncTask<Void, Void, List<AudioBean>>() {
+                @Override
+                protected List<AudioBean> doInBackground(Void... params) {
+                    return FileMusicScanManager.getInstance().scanMusic(MusicPlayerActivity.this);
+                }
+
+                @Override
+                protected void onPostExecute(List<AudioBean> musicList) {
+                    //然后添加所有扫描到的音乐
+                    VideoLogUtils.d("onPostExecute" + musicList.size());
+                    BaseAppHelper.get().setMusicList(musicList);
+                    List<AudioBean> netMusic = new ArrayList<>();
+                    AudioBean audioBean1 = new AudioBean();
+                    audioBean1.setPath("http://img.zhugexuetang.com/lleXB2SNF5UFp1LfNpPI0hsyQjNs");
+                    audioBean1.setId("1");
+                    audioBean1.setTitle("音频1");
+                    musicList.add(audioBean1);
+                    AudioBean audioBean2 = new AudioBean();
+                    audioBean2.setPath("http://img.zhugexuetang.com/ljUa-X-oDbLHu7n9AhkuMLu2Yz3k");
+                    audioBean2.setId("2");
+                    audioBean2.setTitle("音频2");
+                    musicList.add(audioBean2);
+                    AudioBean audioBean3 = new AudioBean();
+                    audioBean3.setPath("http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4");
+                    audioBean3.setId("3");
+                    audioBean3.setTitle("音频3");
+                    netMusic.add(audioBean1);
+                    netMusic.add(audioBean2);
+                    netMusic.add(audioBean3);
+                    BaseAppHelper.get().addMusicList(netMusic);
+                    musicAdapter.notifyDataSetChanged();
+                }
+            }.execute();
+        } else {
+            //当在播放音频详细页面切换歌曲的时候,需要刷新底部控制器,和音频详细页面的数据
+            List<AudioBean> musicList = BaseAppHelper.get().getMusicList();
+            PlayAudioService musicService = BaseAppHelper.get().getMusicService();
+            if(musicList.size()>0){
+                int mPlayPosition;
+                if (musicService.getPlayingMusic() != null ) {
+                    mPlayPosition = musicService.getPlayingPosition();
+                } else {
+                    mPlayPosition = 0;
+                }
+                onChangeImpl(musicList.get(mPlayPosition));
+            }else {
+                onChangeImpl(musicService.getPlayingMusic());
+            }
+        }
     }
 
     @Override
     public void onClick(View v) {
         switch (v.getId()){
-            case R.id.tv_1:
-                BaseAppHelper.get().getMusicService().play(0);
-                break;
-            case R.id.tv_2:
-
-                break;
-            case R.id.tv_3:
-
-                break;
             case R.id.tv_start:
                 BaseAppHelper.get().getMusicService().start();
                 break;
-            case R.id.tv_stop:
-                BaseAppHelper.get().getMusicService().stop();
-                break;
-            case R.id.tv_next:
-                BaseAppHelper.get().getMusicService().next();
-                break;
-            case R.id.tv_pre:
-                BaseAppHelper.get().getMusicService().prev();
-                break;
             case R.id.fl_play_bar:
                 showPlayingFragment();
                 break;
+            case R.id.iv_play_bar_next:
+                BaseAppHelper.get().getMusicService().next();
+                break;
             case R.id.iv_play_bar_play:
                 if (BaseAppHelper.get().getMusicService().isDefault()) {
                     if (BaseAppHelper.get().getMusicList().size() > 0) {
@@ -156,7 +204,11 @@ public class MusicPlayerActivity extends AppCompatActivity implements View.OnCli
              */
             @Override
             public void onChange(AudioBean music) {
+                VideoLogUtils.d("OnPlayerEventListener   onChange ");
                 onChangeImpl(music);
+                if (mPlayFragment!=null){
+                    mPlayFragment.onChange(music);
+                }
             }
 
             /**
@@ -165,7 +217,11 @@ public class MusicPlayerActivity extends AppCompatActivity implements View.OnCli
              */
             @Override
             public void onPlayerStart() {
+                VideoLogUtils.d("OnPlayerEventListener   onPlayerStart ");
                 mIvPlayBarPlay.setSelected(true);
+                if (mPlayFragment!=null){
+                    mPlayFragment.onPlayerStart();
+                }
             }
 
             /**
@@ -174,7 +230,11 @@ public class MusicPlayerActivity extends AppCompatActivity implements View.OnCli
              */
             @Override
             public void onPlayerPause() {
+                VideoLogUtils.d("OnPlayerEventListener   onPlayerPause ");
                 mIvPlayBarPlay.setSelected(false);
+                if (mPlayFragment!=null){
+                    mPlayFragment.onPlayerPause();
+                }
             }
 
             /**
@@ -184,11 +244,16 @@ public class MusicPlayerActivity extends AppCompatActivity implements View.OnCli
             @Override
             public void onUpdateProgress(int progress) {
                 mPbPlayBar.setProgress(progress);
+                if (mPlayFragment!=null){
+                    mPlayFragment.onUpdateProgress(progress);
+                }
             }
 
             @Override
             public void onBufferingUpdate(int percent) {
-
+                if (mPlayFragment != null && mPlayFragment.isAdded()) {
+                    mPlayFragment.onBufferingUpdate(percent);
+                }
             }
 
             /**
@@ -213,11 +278,19 @@ public class MusicPlayerActivity extends AppCompatActivity implements View.OnCli
         if (music == null) {
             return;
         }
+        PlayAudioService musicService = BaseAppHelper.get().getMusicService();
+        Bitmap cover = CoverLoader.getInstance().loadThumbnail(music);
+        mIvPlayBarCover.setImageBitmap(cover);
         mTvPlayBarTitle.setText(music.getTitle());
-        mIvPlayBarPlay.setSelected(BaseAppHelper.get().getMusicService().isPlaying() || BaseAppHelper.get().getMusicService().isPreparing());
+        mIvPlayBarPlay.setSelected(musicService.isPlaying() || musicService.isPreparing());
         //更新进度条
         mPbPlayBar.setMax((int) music.getDuration());
-        mPbPlayBar.setProgress((int) BaseAppHelper.get().getMusicService().getCurrentPosition());
+        mPbPlayBar.setProgress((int) musicService.getCurrentPosition());
+
+
+        recyclerView.scrollToPosition(musicService.getPlayingPosition());
+        musicAdapter.updatePlayingPosition(musicService.getPlayingPosition());
+        musicAdapter.notifyDataSetChanged();
     }
 
 

+ 2 - 4
Demo/src/main/java/com/yc/ycvideoplayer/music/PlayMusicFragment.java

@@ -193,6 +193,7 @@ public class PlayMusicFragment extends Fragment implements View.OnClickListener,
     }
 
     private void initFindById(View view) {
+        ivPlayPageBg = view.findViewById(R.id.iv_play_page_bg);
         llContent = view.findViewById(R.id.ll_content);
         ivBack = view.findViewById(R.id.iv_back);
         tvTitle = view.findViewById(R.id.tv_title);
@@ -398,16 +399,13 @@ public class PlayMusicFragment extends Fragment implements View.OnClickListener,
         setCoverAndBg(playingMusic);
         if (BaseAppHelper.get().getMusicService().isPlaying() || BaseAppHelper.get().getMusicService().isPreparing()) {
             ivPlay.setSelected(true);
-            //mAlbumCoverView.start();
         } else {
             ivPlay.setSelected(false);
-            //mAlbumCoverView.pause();
         }
     }
 
     private void setCoverAndBg(AudioBean music) {
-        //mAlbumCoverView.setCoverBitmap(CoverLoader.getInstance().loadRound(music));
-        //ivPlayPageBg.setImageBitmap(CoverLoader.getInstance().loadBlur(music));
+        ivPlayPageBg.setImageBitmap(CoverLoader.getInstance().loadBlur(music));
     }
 
 

BIN
Demo/src/main/res/drawable-xxhdpi/ic_music_list_icon_more.png


+ 12 - 0
Demo/src/main/res/layout/activity_splash.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:src="@drawable/play_page_default_bg"/>
+
+</LinearLayout>

+ 79 - 0
Demo/src/main/res/layout/item_local_music.xml

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="70dp"
+    android:gravity="center_vertical"
+    android:orientation="horizontal">
+
+    <View
+        android:id="@+id/v_playing"
+        android:layout_width="3dp"
+        android:layout_height="50dp"
+        android:layout_marginEnd="3dp"
+        android:background="@color/redTab"
+        android:contentDescription="@null"
+        android:visibility="invisible" />
+
+    <ImageView
+        android:id="@+id/iv_cover"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:layout_marginStart="10dp"
+        android:scaleType="fitXY"
+        android:src="@drawable/default_cover" />
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="70dp"
+        android:layout_marginStart="10dp">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center_vertical"
+            android:orientation="horizontal">
+            <LinearLayout
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:orientation="vertical">
+                <TextView
+                    android:id="@+id/tv_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:ellipsize="end"
+                    android:singleLine="true"
+                    android:text="歌曲"
+                    android:textColor="@color/blackText"
+                    android:textSize="16sp" />
+
+                <TextView
+                    android:id="@+id/tv_artist"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="5dp"
+                    android:ellipsize="end"
+                    android:singleLine="true"
+                    android:text="歌手 - 专辑"
+                    android:textColor="@color/gray2"
+                    android:textSize="12sp" />
+            </LinearLayout>
+
+            <ImageView
+                android:id="@+id/iv_more"
+                android:layout_width="50dp"
+                android:layout_height="match_parent"
+                android:contentDescription="@null"
+                android:scaleType="centerInside"
+                android:src="@drawable/ic_music_list_icon_more" />
+        </LinearLayout>
+
+        <View
+            android:id="@+id/v_divider"
+            android:layout_width="match_parent"
+            android:layout_height="1px"
+            android:layout_gravity="bottom"
+            android:background="?android:attr/listDivider" />
+    </FrameLayout>
+
+</LinearLayout>

+ 1 - 0
Demo/src/main/res/values/strings.xml

@@ -8,5 +8,6 @@
     <string name="retry">重试</string>
     <string name="loading">加载中</string>
     <string name="error">发生错误</string>
+    <string name="easy_permissions">该APP需要您允许访问设备上的文件的权限</string>
 
 </resources>

+ 2 - 0
MusicPlayer/src/main/AndroidManifest.xml

@@ -2,6 +2,8 @@
     package="com.yc.music">
 
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <!--识别音频文件权限-->
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
 
     <application>
         <service android:name=".service.PlayAudioService" />

+ 2 - 2
MusicPlayer/src/main/java/com/yc/music/receiver/NotificationStatusBarReceiver.java

@@ -27,8 +27,8 @@ public class NotificationStatusBarReceiver extends BroadcastReceiver {
             PlayAudioService.startCommand(context, MusicPlayAction.TYPE_NEXT);
             VideoLogUtils.e("NotifiyStatusBarReceiver"+"下一首");
         } else if (TextUtils.equals(extra, MusicPlayAction.TYPE_START_PAUSE)) {
-            if(BaseAppHelper.get().getPlayService()!=null){
-                boolean playing = BaseAppHelper.get().getPlayService().isPlaying();
+            if(BaseAppHelper.get().getMusicService()!=null){
+                boolean playing = BaseAppHelper.get().getMusicService().isPlaying();
                 if(playing){
                     VideoLogUtils.e("NotifiyStatusBarReceiver"+"暂停");
                 }else {

+ 1 - 1
MusicPlayer/src/main/java/com/yc/music/service/PlayAudioService.java

@@ -78,9 +78,9 @@ public class PlayAudioService extends AbsAudioService {
      */
     @Override
     public void onCreate() {
-        super.onCreate();
         mDelegate = new PlayAudioDelegate(new PlayAudioImpl());
         mDelegate.init(this);
+        super.onCreate();
         initQuitTimer();
     }
 

+ 23 - 7
MusicPlayer/src/main/java/com/yc/music/tool/BaseAppHelper.java

@@ -1,6 +1,7 @@
 package com.yc.music.tool;
 
 import android.annotation.SuppressLint;
+import android.content.Context;
 
 import com.yc.music.model.AudioBean;
 import com.yc.music.service.PlayAudioService;
@@ -27,6 +28,10 @@ public class BaseAppHelper {
      * 本地歌曲列表
      */
     private final List<AudioBean> mMusicList = new ArrayList<>();
+    /**
+     * 全局上下文
+     */
+    private Context mContext;
 
     private BaseAppHelper() {
         //这里可以做一些初始化的逻辑
@@ -45,7 +50,7 @@ public class BaseAppHelper {
      * 获取PlayService对象
      * @return              返回PlayService对象
      */
-    public PlayAudioService getPlayService() {
+    private PlayAudioService getPlayService() {
         return mPlayService;
     }
 
@@ -74,17 +79,28 @@ public class BaseAppHelper {
         mMusicList.addAll(list);
     }
 
+
+    /**
+     * 设置音频结合
+     * @param list              音频集合
+     */
+    public void addMusicList(List<AudioBean> list){
+        mMusicList.addAll(list);
+    }
+
     /**
      * 获取到播放音乐的服务
      * @return              PlayService对象
      */
     public PlayAudioService getMusicService () {
-        PlayAudioService playService = BaseAppHelper.get().getPlayService();
-        if (playService == null) {
-            //待解决:当长期处于后台,如何保活?避免service被杀死……
-            throw new NullPointerException("play service is null");
-        }
-        return playService;
+        return getPlayService();
     }
 
+    public void setContext(Context context){
+        this.mContext = context;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
 }

+ 261 - 0
MusicPlayer/src/main/java/com/yc/music/utils/ImageUtils.java

@@ -0,0 +1,261 @@
+package com.yc.music.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+
+import androidx.annotation.Nullable;
+
+
+/**
+ * 图像工具类
+ */
+public class ImageUtils {
+
+    private static final int BLUR_RADIUS = 50;
+
+    @Nullable
+    public static Bitmap blur(Bitmap source) {
+        if (source == null) {
+            return null;
+        }
+        try {
+            return blur(source, BLUR_RADIUS);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return source;
+        }
+    }
+
+    private static Bitmap blur(Bitmap source, int radius) {
+        Bitmap bitmap = source.copy(source.getConfig(), true);
+
+        if (radius < 1) {
+            return null;
+        }
+
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+
+        int[] pix = new int[w * h];
+        bitmap.getPixels(pix, 0, w, 0, 0, w, h);
+
+        int wm = w - 1;
+        int hm = h - 1;
+        int wh = w * h;
+        int div = radius + radius + 1;
+
+        int r[] = new int[wh];
+        int g[] = new int[wh];
+        int b[] = new int[wh];
+        int rSum, gSum, bSum, x, y, i, p, yp, yi, yw;
+        int vMin[] = new int[Math.max(w, h)];
+
+        int divSum = (div + 1) >> 1;
+        divSum *= divSum;
+        int dv[] = new int[256 * divSum];
+        for (i = 0; i < 256 * divSum; i++) {
+            dv[i] = (i / divSum);
+        }
+
+        yw = yi = 0;
+
+        int[][] stack = new int[div][3];
+        int stackPointer;
+        int stackStart;
+        int[] sir;
+        int rbs;
+        int r1 = radius + 1;
+        int rOutSum, gOutSum, bOutSum;
+        int rInSum, gInSum, bInSum;
+
+        for (y = 0; y < h; y++) {
+            rInSum = gInSum = bInSum = rOutSum = gOutSum = bOutSum = rSum = gSum = bSum = 0;
+            for (i = -radius; i <= radius; i++) {
+                p = pix[yi + Math.min(wm, Math.max(i, 0))];
+                sir = stack[i + radius];
+                sir[0] = (p & 0xff0000) >> 16;
+                sir[1] = (p & 0x00ff00) >> 8;
+                sir[2] = (p & 0x0000ff);
+                rbs = r1 - Math.abs(i);
+                rSum += sir[0] * rbs;
+                gSum += sir[1] * rbs;
+                bSum += sir[2] * rbs;
+                if (i > 0) {
+                    rInSum += sir[0];
+                    gInSum += sir[1];
+                    bInSum += sir[2];
+                } else {
+                    rOutSum += sir[0];
+                    gOutSum += sir[1];
+                    bOutSum += sir[2];
+                }
+            }
+            stackPointer = radius;
+
+            for (x = 0; x < w; x++) {
+                r[yi] = dv[rSum];
+                g[yi] = dv[gSum];
+                b[yi] = dv[bSum];
+
+                rSum -= rOutSum;
+                gSum -= gOutSum;
+                bSum -= bOutSum;
+
+                stackStart = stackPointer - radius + div;
+                sir = stack[stackStart % div];
+
+                rOutSum -= sir[0];
+                gOutSum -= sir[1];
+                bOutSum -= sir[2];
+
+                if (y == 0) {
+                    vMin[x] = Math.min(x + radius + 1, wm);
+                }
+                p = pix[yw + vMin[x]];
+
+                sir[0] = (p & 0xff0000) >> 16;
+                sir[1] = (p & 0x00ff00) >> 8;
+                sir[2] = (p & 0x0000ff);
+
+                rInSum += sir[0];
+                gInSum += sir[1];
+                bInSum += sir[2];
+
+                rSum += rInSum;
+                gSum += gInSum;
+                bSum += bInSum;
+
+                stackPointer = (stackPointer + 1) % div;
+                sir = stack[(stackPointer) % div];
+
+                rOutSum += sir[0];
+                gOutSum += sir[1];
+                bOutSum += sir[2];
+
+                rInSum -= sir[0];
+                gInSum -= sir[1];
+                bInSum -= sir[2];
+
+                yi++;
+            }
+            yw += w;
+        }
+        for (x = 0; x < w; x++) {
+            rInSum = gInSum = bInSum = rOutSum = gOutSum = bOutSum = rSum = gSum = bSum = 0;
+            yp = -radius * w;
+            for (i = -radius; i <= radius; i++) {
+                yi = Math.max(0, yp) + x;
+
+                sir = stack[i + radius];
+
+                sir[0] = r[yi];
+                sir[1] = g[yi];
+                sir[2] = b[yi];
+
+                rbs = r1 - Math.abs(i);
+
+                rSum += r[yi] * rbs;
+                gSum += g[yi] * rbs;
+                bSum += b[yi] * rbs;
+
+                if (i > 0) {
+                    rInSum += sir[0];
+                    gInSum += sir[1];
+                    bInSum += sir[2];
+                } else {
+                    rOutSum += sir[0];
+                    gOutSum += sir[1];
+                    bOutSum += sir[2];
+                }
+
+                if (i < hm) {
+                    yp += w;
+                }
+            }
+            yi = x;
+            stackPointer = radius;
+            for (y = 0; y < h; y++) {
+                // Preserve alpha channel: ( 0xff000000 & pix[yi] )
+                pix[yi] = (0xff000000 & pix[yi]) | (dv[rSum] << 16) | (dv[gSum] << 8) | dv[bSum];
+
+                rSum -= rOutSum;
+                gSum -= gOutSum;
+                bSum -= bOutSum;
+
+                stackStart = stackPointer - radius + div;
+                sir = stack[stackStart % div];
+
+                rOutSum -= sir[0];
+                gOutSum -= sir[1];
+                bOutSum -= sir[2];
+
+                if (x == 0) {
+                    vMin[y] = Math.min(y + r1, hm) * w;
+                }
+                p = x + vMin[y];
+
+                sir[0] = r[p];
+                sir[1] = g[p];
+                sir[2] = b[p];
+
+                rInSum += sir[0];
+                gInSum += sir[1];
+                bInSum += sir[2];
+
+                rSum += rInSum;
+                gSum += gInSum;
+                bSum += bInSum;
+
+                stackPointer = (stackPointer + 1) % div;
+                sir = stack[stackPointer];
+
+                rOutSum += sir[0];
+                gOutSum += sir[1];
+                bOutSum += sir[2];
+
+                rInSum -= sir[0];
+                gInSum -= sir[1];
+                bInSum -= sir[2];
+
+                yi += w;
+            }
+        }
+
+        bitmap.setPixels(pix, 0, w, 0, 0, w, h);
+
+        return bitmap;
+    }
+
+    /**
+     * 将图片放大或缩小到指定尺寸
+     */
+    public static Bitmap resizeImage(Bitmap source, int dstWidth, int dstHeight) {
+        if (source == null) {
+            return null;
+        }
+
+        return Bitmap.createScaledBitmap(source, dstWidth, dstHeight, true);
+    }
+
+    /**
+     * 将图片剪裁为圆形
+     */
+    public static Bitmap createCircleImage(Bitmap source) {
+        if (source == null) {
+            return null;
+        }
+
+        int length = Math.min(source.getWidth(), source.getHeight());
+        Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        Bitmap target = Bitmap.createBitmap(length, length, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(target);
+        canvas.drawCircle(source.getWidth() / 2, source.getHeight() / 2, length / 2, paint);
+        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+        canvas.drawBitmap(source, 0, 0, paint);
+        return target;
+    }
+}

+ 2 - 2
VideoView/src/main/java/com/yc/videoview/WindowUtil.java

@@ -20,7 +20,7 @@ public final class WindowUtil {
 
     private static Point sPoint;
 
-    static int getScreenWidth(Context context) {
+    public static int getScreenWidth(Context context) {
         if (sPoint == null) {
             sPoint = new Point();
             WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
@@ -31,7 +31,7 @@ public final class WindowUtil {
         return sPoint.x;
     }
 
-    static int getScreenHeight(Context context) {
+    public static int getScreenHeight(Context context) {
         if (sPoint == null) {
             sPoint = new Point();
             WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);