仿斗鱼直播后台播放功能实现

最近公司项目需要视频后台播放,后台播放关键点:

  • 判断应用是否进入后台
  • 视频如何后台播放
  • 视频后台播放如何弹出Notification通知
  • 使用SharedPreferences记录用户是否设置了后台播放(默认开启后台播放)

判断应用是否进入后台(Application ActivityLifecycleCallbacks)

  • Application生命周期是最长的,由registerActivityLifecycleCallbacks监控当前到底是哪个Activity,并通过mActivityCount记录是否进入后台,进入后台使用发送广播显示notification
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    /**
    * @author maoqitian
    * @Description:
    * @date 2019/1/4 0004 10:30
    */

    public class MyApplication extends Application {

    private static final String TAG = "MyApplication";

    /**
    * 当获取的为0则当前应用在后台,否则不为0则为应用在前台 by maoqitian
    */
    private int mActivityCount = 0;

    /**
    * 是否进入后台
    */
    private boolean isBackground=false;


    @Override
    public void onCreate() {
    super.onCreate();

    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    Log.d(TAG,"onActivityCreated");
    }

    @Override
    public void onActivityStarted(Activity activity) {
    mActivityCount++;
    Log.d(TAG,"onActivityStarted"+"mActivityCount :"+mActivityCount +"activityName:"+ activity.getClass().getSimpleName());
    if(mActivityCount == 1 && isBackground
    && "PlayerActivity".equals(activity.getClass().getSimpleName())
    ){ //只有播放器才进行操作判断
    Log.d(TAG, "onActivityStarted: 播放器进入前台" );
    Toast.makeText(MyApplication.this,"播放器进入前台",Toast.LENGTH_LONG).show();
    isBackground=false;
    }
    }

    @Override
    public void onActivityResumed(Activity activity) {
    Log.d(TAG,"onActivityResumed");
    }

    @Override
    public void onActivityPaused(Activity activity) {
    Log.d(TAG,"onActivityCreated");
    }

    @Override
    public void onActivityStopped(Activity activity) {
    Log.d(TAG,"onActivityStopped");
    mActivityCount--;
    Log.d(TAG,"onActivityStopped"+"mActivityCount :"+mActivityCount+"activityName:"+ activity.getClass().getSimpleName());
    if (mActivityCount <= 0 && !isBackground && isRun(activity)
    && "PlayerActivity".equals(activity.getClass().getSimpleName())
    ) { //只有播放器才进行操作判断,如果应用在后台运行
    Log.e(TAG, "onActivityStarted: 播放器进入后台" );
    isBackground =true;
    //说明应用进入了后台
    PlayerActivity playerActivity = (PlayerActivity) activity;
    Bundle bundle = playerActivity.onNotificationMsg();
    Toast.makeText(MyApplication.this,"播放器进入后台播放",Toast.LENGTH_LONG).show();
    //发送显示 notify 广播
    Intent intent=new Intent(PlayerNotifyBroadcastReceiver.ACTION_NOTIFY_MESSAGE);
    intent.setClass(getApplicationContext(),PlayerNotifyBroadcastReceiver.class);
    Log.e(TAG, "string" +bundle.getString("unitTitle"));
    intent.putExtra("unitTitle",bundle.getString("unitTitle"));
    intent.putExtra("unitImage",bundle.getString("unitImage"));
    sendBroadcast(intent);
    }
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    Log.d(TAG,"onActivitySaveInstanceState");
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    Log.d(TAG,"onActivityDestroyed");
    }
    });
    }


    /**
    * 判断应用是否在运行
    * @param context
    * @return
    */
    private boolean isRun(Context context) {
    ActivityManager activityManager= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    assert activityManager != null;
    List<ActivityManager.RunningTaskInfo> runningTasks = activityManager.getRunningTasks(100);
    boolean isAppRunning = false;
    String MY_PKG_NAME = "mao.com.backgroundplay";
    //100表示取的最大的任务数,info.topActivity表示当前正在运行的Activity,info.baseActivity表系统后台有此进程在运行
    for (ActivityManager.RunningTaskInfo info : runningTasks) {
    if (info.topActivity.getPackageName().equals(MY_PKG_NAME) || info.baseActivity.getPackageName().equals(MY_PKG_NAME)) {
    isAppRunning = true;
    Log.i(TAG,info.topActivity.getPackageName() + " info.baseActivity.getPackageName()="+info.baseActivity.getPackageName());
    break;
    }
    }
    return isAppRunning;
    }
    }

监听后台播放广播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
    /**
* @author maoqitian
* @Description:
* @date 2019/1/4 0004 10:58
*/

public class PlayerNotifyBroadcastReceiver extends BroadcastReceiver {

public static final String ACTION_NOTIFY_MESSAGE = "com.besto.beautifultv.ACTION_NOTIFY_MESSAGE";

@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() == null ) return;
switch (intent.getAction()){
case ACTION_NOTIFY_MESSAGE:
String unitTitle = intent.getStringExtra("unitTitle");
String unitImage = intent.getStringExtra("unitImage");
Class<? extends Activity> aClass = AppManager.getInstance().currentActivity().getClass();
if(aClass != null){
NotificationUtils notificationUtils = NotificationUtils.getInstance();
//注意 RemoteView 不支持ConstraintLayout布局 支持FrameLayout, LinearLayout, RelativeLayout
notificationUtils.init(context,aClass, R.layout.notification_layout);
notificationUtils.showNotification(unitTitle,unitImage);
notificationUtils.getmRemoteViews().setTextViewText(R.id.tv_player_des,"正在后台播放:"+unitTitle);
notificationUtils.update();
}
break;
}
}
}
```
### 当前Activity栈管理(拿到对应的Activity)
```
/**
* @author maoqitian
* @Description: Activity栈管理(对应播放器中进行添加进栈出栈)
* @date 2018/12/29 0029 15:05
*/

public class AppManager {

private static volatile AppManager instance;
private static Stack<Activity> activityStack;

public static AppManager getInstance() {
if (instance == null) {
synchronized (AppManager.class) {
if (instance == null) {
instance = new AppManager();
}
}
}
return instance;
}

/**
* 添加Activity到stack中
*/
public void addActivity(Activity activity) {
if (activityStack == null) {
activityStack = new Stack<>();
}
if (activityStack.contains(activity)) {
activityStack.remove(activity);
}
activityStack.add(activity);
}

/**
* 获取stack中当前的Activity
*/
public Activity currentActivity() {
if (null != activityStack && null != activityStack.lastElement()) {
return activityStack.lastElement();
}
return null;
}

/**
* 移除当前的Activity
*/
public void finishActivity() {
if (null != activityStack && null != activityStack.lastElement()) {
finishActivity(activityStack.lastElement());
}
}

/**
* 移除指定的Activity
*
* @param activity 指定的Activity
*/
public void finishActivity(Activity activity) {
if (activity != null) {
activityStack.remove(activity);
activity.finish();
activity = null;
}
}

/**
* 移除指定Class所对应的Activity
*/
public void finishActivity(Class<?> cls) {
Stack<Activity> activitys = new Stack<Activity>();
for (Activity activity : activityStack) {
if (activity.getClass().equals(cls)) {
activitys.add(activity);
}
}

for (Activity activity : activitys) {
finishActivity(activity);
}
}

/**
* 移除所有的Activity
*/
public void finishAllActivity() {
if (activityStack == null)
return;

for (int i = 0, size = activityStack.size(); i < size; i++) {
if (null != activityStack.get(i)) {
activityStack.get(i).finish();
}
}
activityStack.clear();
}
}

添加Notification通知(Notification如何加载网络图片(Glide target 或者 直接remoteview 直接设置 bitmap))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* @author maoqitian
* @Description: 通知栏工具类
* @date 2018/12/29 0029 11:12
*/

public class NotificationUtils {

private NotificationManager mNotificationManager;
private Notification mNotification;
private Intent mNotificationIntent;
private int notifyId = 1;
private String mChannelId = "bgPlayChannel";

private RemoteViews mRemoteViews;

private static volatile NotificationUtils mInstance;

private int mLayoutId;

private Context mContext;

private NotificationUtils(){

}
//双重效验锁实现单例
public static NotificationUtils getInstance(){
if(mInstance == null){
synchronized (NotificationUtils.class){
if(mInstance == null){
mInstance = new NotificationUtils();
}
}
}
return mInstance;
}

public void init(Context context, @NonNull Class<?> intentActivity,int layoutId){
mContext = context;
mLayoutId = layoutId;
mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationIntent = new Intent(mContext, intentActivity);
mNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);
mRemoteViews = new RemoteViews(mContext.getPackageName(),mLayoutId);
if (notifyId <= 4) {
//最多显示4条通知
notifyId += 1;
}
}

public void showNotification(@NonNull String contentText,String iamgeUrl){
PendingIntent _pendingIntent = PendingIntent.getActivity(mContext, notifyId, mNotificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);

Notification.Builder builder = null;
//notification channel work
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(mChannelId, "liangTV",
NotificationManager.IMPORTANCE_HIGH);
channel.enableLights(true);//是否在桌面icon右上角展示小红点
channel.setLightColor(Color.RED);//小红点颜色
channel.setShowBadge(true); //是否在久按桌面图标时显示此渠道的通知
mNotificationManager.createNotificationChannel(channel);
builder = new Notification.Builder(mContext,mChannelId);
} else {
builder = new Notification.Builder(mContext);
}
builder.setSmallIcon(R.drawable.ic_logo);
builder.setContentIntent(_pendingIntent);
builder.setTicker(contentText);
//builder.setCustomContentView(mRemoteViews);
// mNotification = builder.build();
//Notification.FLAG_ONLY_ALERT_ONCE 避免8.0在进度更新时候(notify)中多次响铃
mNotification = builder.build();
mNotification.contentView=mRemoteViews;
mNotification.flags = Notification.FLAG_NO_CLEAR|Notification.FLAG_ONLY_ALERT_ONCE;
mNotification.icon = R.drawable.ic_logo;
//使用NotificationTarget(Glide)来加载图片
NotificationTarget notificationTarget = new NotificationTarget(mContext,mRemoteViews,R.id.notification_Image_play,mNotification,notifyId);
Glide.with(mContext.getApplicationContext()) // safer!
.load(iamgeUrl)
.asBitmap()
.into( notificationTarget );
mNotificationManager.notify(notifyId, mNotification);
}

//清除所有推送通知
public void clearAllNotification() {
if (mNotificationManager != null)
mNotificationManager.cancelAll();
}

public RemoteViews getmRemoteViews() {
return mRemoteViews;
}

public void update(){
mNotificationManager.notify(notifyId,mNotification);
}
}

视频后台播放处理(Activity 生命周期 surfacedestory 中不进行释放 player)

  • 对应播放器Activity的各个生命周期方法做相应的播放位置记录(onRestart恢复视频播放 onDestory释放相应资源 onBackPressed中也得需要及时pause视频)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void surfaceDestroyed(SurfaceHolder holder) {
    ......
    if(ismPauseInBackground()){
    //尝试进入后台播放不释放播放器,继续播放音频
    mMediaPlayer.setDisplay(null);
    }else {
    .....
    }
    }

手动设置是否进行后台播放 (使用SharedPreferences)

    • 使用SharedPreferences,获取状态
    1
    SharedPreferences toggleBtnSP = this.getSharedPreferences("toggleButtonState", Context.MODE_PRIVATE);
-------------本文结束感谢阅读-------------
0%