Merge branch 'fix_loginMultiRoom_bug'
This commit is contained in:
2
.idea/deploymentTargetDropDown.xml
generated
2
.idea/deploymentTargetDropDown.xml
generated
@@ -12,6 +12,6 @@
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</runningDeviceTargetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2021-11-04T08:38:51.555303Z" />
|
||||
<timeTargetWasSelectedWithDropDown value="2021-11-04T14:09:00.912888Z" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -28,7 +28,9 @@ public class LiveStreamManagerImpl implements ILiveStreamManager {
|
||||
private final Application mApplication;
|
||||
|
||||
// 循环上报摄像头可直播状态间隔时间
|
||||
private static final int PUSH_CAM_TIME = 60 * 1000;
|
||||
private static final int PUSH_CAM_TIME = 10 * 1000;
|
||||
// 检测房间在线人数,<=1代表无人观看,即可以关闭推流,1个人代表自己
|
||||
private static final int PUSH_CHECK_TIME = 60 * 1000;
|
||||
private static volatile int sCam1LiveStatus = 0, sCam2LiveStatus = 0;
|
||||
private static volatile int sCam1AvailableStatus = 0, sCam2AvailableStatus = 0;
|
||||
private static final int PUSH_START = 0; // 开始
|
||||
@@ -36,7 +38,8 @@ public class LiveStreamManagerImpl implements ILiveStreamManager {
|
||||
private static final int PUSH_FORCED_STOP = 2; // 强制结束直播,不考虑是否有观众
|
||||
private static final String C1 = "C_1"; //前置摄像头
|
||||
private static final String C2 = "C_2"; //后置摄像头
|
||||
private Handler mHandler; // 循环上报摄像头状态
|
||||
private Handler mHandlerPushCameraStatus; // 循环上报摄像头状态
|
||||
private Handler mHandlerCheckOnlineNum; // 检测在线观看人数
|
||||
private final SocketMsgUtils mSocketMsgUtils; // Socket 长链接
|
||||
private MoGoLiveManager mLivePusher; // 自研直播SDK
|
||||
|
||||
@@ -57,7 +60,8 @@ public class LiveStreamManagerImpl implements ILiveStreamManager {
|
||||
|
||||
private LiveStreamManagerImpl(Application application, String devicesId) {
|
||||
mApplication = application;
|
||||
mHandler = new Handler();
|
||||
mHandlerPushCameraStatus = new Handler();
|
||||
mHandlerCheckOnlineNum = new Handler();
|
||||
cameraStatusManager = CameraStatusManager.getInstance();
|
||||
// 初始化直播推流
|
||||
initLivePush(devicesId);
|
||||
@@ -78,6 +82,8 @@ public class LiveStreamManagerImpl implements ILiveStreamManager {
|
||||
});
|
||||
// 初始化查询摄像头状态
|
||||
restartCamStatusLoop();
|
||||
// 开启房间人数检测
|
||||
restartCheckOnlineNumLoop();
|
||||
|
||||
}
|
||||
|
||||
@@ -155,8 +161,10 @@ public class LiveStreamManagerImpl implements ILiveStreamManager {
|
||||
@Override
|
||||
public void release() {
|
||||
try {
|
||||
mHandler.removeCallbacks(mCamStatusRun);
|
||||
mHandler = null;
|
||||
mHandlerPushCameraStatus.removeCallbacks(mCamStatusRun);
|
||||
mHandlerPushCameraStatus = null;
|
||||
mHandlerCheckOnlineNum.removeCallbacks(mOnlineNumRun);
|
||||
mHandlerCheckOnlineNum = null;
|
||||
mSocketMsgUtils.release();
|
||||
sCam1LiveStatus = 0;
|
||||
sCam2LiveStatus = 0;
|
||||
@@ -166,13 +174,24 @@ public class LiveStreamManagerImpl implements ILiveStreamManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启摄像头状态循环
|
||||
* 开启摄像头状态循环检测
|
||||
*/
|
||||
private void restartCamStatusLoop() {
|
||||
Logger.i(TAG, "摄像头状态循环");
|
||||
if (mHandler != null) {
|
||||
mHandler.removeCallbacks(mCamStatusRun);
|
||||
mHandler.postDelayed(mCamStatusRun, PUSH_CAM_TIME);
|
||||
Logger.i(TAG, "开启摄像头状态循环检测");
|
||||
if (mHandlerPushCameraStatus != null) {
|
||||
mHandlerPushCameraStatus.removeCallbacks(mCamStatusRun);
|
||||
mHandlerPushCameraStatus.postDelayed(mCamStatusRun, PUSH_CAM_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启房间人数检测
|
||||
*/
|
||||
private void restartCheckOnlineNumLoop() {
|
||||
Logger.i(TAG, "开启房间人数检测");
|
||||
if (mHandlerCheckOnlineNum != null) {
|
||||
mHandlerCheckOnlineNum.removeCallbacks(mOnlineNumRun);
|
||||
mHandlerCheckOnlineNum.postDelayed(mOnlineNumRun, PUSH_CHECK_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +202,12 @@ public class LiveStreamManagerImpl implements ILiveStreamManager {
|
||||
Logger.i(TAG, "循环同步摄像头状态");
|
||||
uploadCamStatus(sCam1AvailableStatus, sCam2AvailableStatus);
|
||||
restartCamStatusLoop();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private final Runnable mOnlineNumRun = () -> {
|
||||
// 判断当前观众的个数,如果没人观看了的情况则停止直播
|
||||
Logger.i(TAG, "直播状态为:" + mLivePusher.getLiveStatusModel().isPushing() +
|
||||
" 观众人数:" + mLivePusher.getLiveStatusModel().getOnlineNumber());
|
||||
@@ -191,6 +216,7 @@ public class LiveStreamManagerImpl implements ILiveStreamManager {
|
||||
mLivePusher.getLiveStatusModel().getOnlineNumber() <= 1) {
|
||||
livePushHandler(PUSH_STOP, C1);
|
||||
}
|
||||
restartCheckOnlineNumLoop();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.mogo.cloud.live.manager;
|
||||
|
||||
import android.app.Application;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
import com.mogo.cloud.live.listener.ILiveCurrentRoomStatusListener;
|
||||
@@ -11,7 +12,6 @@ import com.mogo.cloud.live.listener.ILiveRoomPersonListener;
|
||||
import com.mogo.cloud.live.listener.ILiveStatusListener;
|
||||
import com.mogo.cloud.live.model.LiveStatusModel;
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClientConfig;
|
||||
import com.mogo.cloud.utils.logger.Logger;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -130,7 +130,7 @@ public class MoGoLiveManager {
|
||||
}
|
||||
|
||||
private MoGoLiveManager() {
|
||||
Logger.i(TAG, "初始化 MoGoLiveManager");
|
||||
Log.i(TAG, "初始化 MoGoLiveManager");
|
||||
mLiveStatusModel = new LiveStatusModel();
|
||||
|
||||
zeGoRoomConfig = new ZegoRoomConfig();
|
||||
@@ -172,7 +172,7 @@ public class MoGoLiveManager {
|
||||
@Override
|
||||
public void onDebugError(int errorCode, String funcName, String info) {
|
||||
super.onDebugError(errorCode, funcName, info);
|
||||
Logger.i(TAG, "调试错误信息回调 onDebugError errorCode : " + errorCode);
|
||||
Log.i(TAG, "调试错误信息回调 onDebugError errorCode : " + errorCode);
|
||||
for (ILiveProgressListener iLiveProgressListener : mProgressListener) {
|
||||
iLiveProgressListener.onDebugError(errorCode, funcName, info);
|
||||
}
|
||||
@@ -184,7 +184,7 @@ public class MoGoLiveManager {
|
||||
@Override
|
||||
public void onEngineStateUpdate(ZegoEngineState state) {
|
||||
super.onEngineStateUpdate(state);
|
||||
Logger.i(TAG, "音视频引擎状态更新回调 onEngineStateUpdate state : " + state.name());
|
||||
Log.i(TAG, "音视频引擎状态更新回调 onEngineStateUpdate state : " + state.name());
|
||||
if (state == ZegoEngineState.START) {
|
||||
for (ILiveProgressListener iLiveProgressListener : mProgressListener) {
|
||||
iLiveProgressListener.onEngineStart();
|
||||
@@ -202,7 +202,7 @@ public class MoGoLiveManager {
|
||||
super.onRoomStateUpdate(roomID, state, errorCode, extendedData);
|
||||
// 这里回调的是当前用户的房间状态
|
||||
if (mLiveStatusModel.getCurrentRoomId().equals(roomID)) {
|
||||
Logger.i(TAG, "房间状态变化通知 onRoomStateUpdate currentRoomId : " + roomID +
|
||||
Log.i(TAG, "房间状态变化通知 onRoomStateUpdate currentRoomId : " + roomID +
|
||||
" state:" + state +
|
||||
" errorCode:" + errorCode);
|
||||
|
||||
@@ -226,7 +226,7 @@ public class MoGoLiveManager {
|
||||
}
|
||||
// 这里回调的是登录的他人房间的状态
|
||||
if (mLiveStatusModel.getMultiRoomId().equals(roomID)) {
|
||||
Logger.i(TAG, "这里回调的是登录的他人房间的状态 onRoomStateUpdate multiRoomId : " + roomID +
|
||||
Log.i(TAG, "这里回调的是登录的他人房间的状态 onRoomStateUpdate multiRoomId : " + roomID +
|
||||
" state:" + state +
|
||||
" errorCode:" + errorCode);
|
||||
// 设置第二个房间登录状态
|
||||
@@ -257,7 +257,7 @@ public class MoGoLiveManager {
|
||||
// 更新真实的在线人数
|
||||
mLiveStatusModel.setOnlineNumber(count);
|
||||
|
||||
Logger.i(TAG, "房间内当前在线用户数量回调 onRoomOnlineUserCountUpdate roomID : " + roomID +
|
||||
Log.i(TAG, "房间内当前在线用户数量回调 onRoomOnlineUserCountUpdate roomID : " + roomID +
|
||||
" , online user number : " + mLiveStatusModel.getOnlineNumber());
|
||||
if (mRoomPersonListener != null) {
|
||||
mRoomPersonListener.onRoomOnlineUserCountUpdate(count);
|
||||
@@ -278,7 +278,7 @@ public class MoGoLiveManager {
|
||||
mLiveStatusModel.setOnlineNumber(mLiveStatusModel.getOnlineNumber() - userList.size());
|
||||
}
|
||||
|
||||
Logger.i(TAG, "房间内其他用户增加或减少的通知回调 onRoomUserUpdate roomId : " + roomID +
|
||||
Log.i(TAG, "房间内其他用户增加或减少的通知回调 onRoomUserUpdate roomId : " + roomID +
|
||||
" , updateType : " + updateType.name() +
|
||||
" , online user number : " + mLiveStatusModel.getOnlineNumber());
|
||||
if (mRoomPersonListener != null) {
|
||||
@@ -297,7 +297,7 @@ public class MoGoLiveManager {
|
||||
if (mLiveStatusModel.getCurrentStreamId().equals(streamID)) {
|
||||
// 判断不是非推流状态则为推流
|
||||
mLiveStatusModel.setPushing(state != ZegoPublisherState.NO_PUBLISH);
|
||||
Logger.i(TAG, "推流状态回调 onPublisherStateUpdate streamID : " + streamID +
|
||||
Log.i(TAG, "推流状态回调 onPublisherStateUpdate streamID : " + streamID +
|
||||
" , isPushing : " + mLiveStatusModel.isPushing() +
|
||||
" , state : " + state.name() +
|
||||
" , errorCode : " + errorCode);
|
||||
@@ -323,7 +323,7 @@ public class MoGoLiveManager {
|
||||
if (mLiveStatusModel.getMultiStreamId().equals(streamID)) {
|
||||
// 如果不是非播放状态则判定为直播状态
|
||||
mLiveStatusModel.setPlaying(state != ZegoPlayerState.NO_PLAY);
|
||||
Logger.i(TAG, " 拉流状态变更回调 onPlayerStateUpdate streamId : " + streamID +
|
||||
Log.i(TAG, " 拉流状态变更回调 onPlayerStateUpdate streamId : " + streamID +
|
||||
" , isPlaying : " + mLiveStatusModel.isPlaying() +
|
||||
" , state : " + state.name() +
|
||||
" , errorCode : " + errorCode +
|
||||
@@ -398,7 +398,7 @@ public class MoGoLiveManager {
|
||||
* 初始化自定义采集
|
||||
*/
|
||||
private void initExpressEngine() {
|
||||
Logger.i(TAG, "initCustomVideoCapture 初始化引擎");
|
||||
Log.i(TAG, "initCustomVideoCapture 初始化引擎");
|
||||
mLiveStatusModel.setExpressEngineCanUse(true);
|
||||
// 创建 enging 对象, appID, appSign 为开发者在 ZEGO 管理控制台申请的凭证信息,
|
||||
// 未上线的开发者 isTestEnvironment 为 true, application 为安卓应用的上下文
|
||||
@@ -458,14 +458,14 @@ public class MoGoLiveManager {
|
||||
@Override
|
||||
public void onStart(ZegoPublishChannel channel) {
|
||||
super.onStart(channel);
|
||||
Logger.i(TAG, "SDK 通知将要开始采集视频帧 setCustomVideoCaptureHandler onStart");
|
||||
Log.i(TAG, "SDK 通知将要开始采集视频帧 setCustomVideoCaptureHandler onStart");
|
||||
mLiveStatusModel.setCaptureStatus(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(ZegoPublishChannel channel) {
|
||||
super.onStop(channel);
|
||||
Logger.i(TAG, "SDK 通知将要停止采集视频帧 setCustomVideoCaptureHandler onStop");
|
||||
Log.i(TAG, "SDK 通知将要停止采集视频帧 setCustomVideoCaptureHandler onStop");
|
||||
mLiveStatusModel.setCaptureStatus(false);
|
||||
}
|
||||
});
|
||||
@@ -486,7 +486,7 @@ public class MoGoLiveManager {
|
||||
}
|
||||
mLiveStatusModel.setCurrentRoomId(ROOM_ID_PREFIX + userId);
|
||||
mLiveStatusModel.setCurrentStreamId(STREAM_ID_PREFIX + userId);
|
||||
Logger.i(TAG,
|
||||
Log.i(TAG,
|
||||
"尝试进入自己房间 loginRoom userId : " + userId
|
||||
+ " currentRoomId:" + mLiveStatusModel.getCurrentRoomId()
|
||||
+ " currentStreamId:" + mLiveStatusModel.getCurrentStreamId());
|
||||
@@ -500,20 +500,20 @@ public class MoGoLiveManager {
|
||||
* 退出自己房间
|
||||
*/
|
||||
public synchronized void logoutCurrentRoom() {
|
||||
Logger.i(TAG, "尝试退出自己房间 logoutRoom : " + mLiveStatusModel.getCurrentRoomId());
|
||||
Log.i(TAG, "尝试退出自己房间 logoutRoom : " + mLiveStatusModel.getCurrentRoomId());
|
||||
if (mExpressEngine != null) {
|
||||
if (!mLiveStatusModel.isLoginCurrentRoom()) {
|
||||
Logger.w(TAG, "还未登录自己房间");
|
||||
Log.w(TAG, "还未登录自己房间");
|
||||
return;
|
||||
}
|
||||
if (mLiveStatusModel.isPlaying()) {
|
||||
Logger.w(TAG, "处于播放中,不可退出房间");
|
||||
Log.w(TAG, "处于播放中,不可退出房间");
|
||||
return;
|
||||
}
|
||||
Logger.i(TAG, "执行退出房间操作");
|
||||
Log.i(TAG, "执行退出房间操作");
|
||||
mExpressEngine.logoutRoom(mLiveStatusModel.getCurrentRoomId());
|
||||
} else {
|
||||
Logger.w(TAG, "ZeGo 引擎未初始化");
|
||||
Log.w(TAG, "ZeGo 引擎未初始化");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,14 +528,14 @@ public class MoGoLiveManager {
|
||||
}
|
||||
mLiveStatusModel.setMultiRoomId(ROOM_ID_PREFIX + otherRoomId);
|
||||
mLiveStatusModel.setMultiStreamId(STREAM_ID_PREFIX + otherRoomId);
|
||||
Logger.i(TAG,
|
||||
Log.i(TAG,
|
||||
"尝试进入多房间 loginMultiRoom multiRoomId : " + mLiveStatusModel.getMultiRoomId()
|
||||
+ " currentStreamId : " + mLiveStatusModel.getMultiStreamId());
|
||||
if (!mLiveStatusModel.isLoginMultiRoom()) {
|
||||
Logger.i(TAG, "执行登录多房间");
|
||||
Log.i(TAG, "执行登录多房间");
|
||||
mExpressEngine.loginMultiRoom(mLiveStatusModel.getMultiRoomId(), zeGoRoomConfig);
|
||||
} else {
|
||||
Logger.w(TAG, "已经登录了多房间");
|
||||
Log.w(TAG, "已经登录了多房间");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,16 +543,16 @@ public class MoGoLiveManager {
|
||||
* 退出多房间
|
||||
*/
|
||||
public synchronized void logoutMultiRoom() {
|
||||
Logger.i(TAG, "尝试退出多房间 logoutMultiRoom multiRoomId : " + mLiveStatusModel.getMultiRoomId());
|
||||
Log.i(TAG, "尝试退出多房间 logoutMultiRoom multiRoomId : " + mLiveStatusModel.getMultiRoomId());
|
||||
if (mExpressEngine != null) {
|
||||
if (mLiveStatusModel.isLoginMultiRoom()) {
|
||||
Logger.i(TAG, "执行退出多房间");
|
||||
Log.i(TAG, "执行退出多房间");
|
||||
mExpressEngine.logoutRoom(mLiveStatusModel.getMultiRoomId());
|
||||
} else {
|
||||
Logger.w(TAG, "还未登录多房间");
|
||||
Log.w(TAG, "还未登录多房间");
|
||||
}
|
||||
} else {
|
||||
Logger.w(TAG, "ZeGo 引擎未初始化");
|
||||
Log.w(TAG, "ZeGo 引擎未初始化");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +560,7 @@ public class MoGoLiveManager {
|
||||
* 开始推流到ZeGo
|
||||
*/
|
||||
public void startPublish() {
|
||||
Logger.i(TAG, "开始推流到ZeGo startPublish currentStreamId : " + mLiveStatusModel.getCurrentStreamId());
|
||||
Log.i(TAG, "开始推流到ZeGo startPublish currentStreamId : " + mLiveStatusModel.getCurrentStreamId());
|
||||
mExpressEngine.startPublishingStream(mLiveStatusModel.getCurrentStreamId());
|
||||
}
|
||||
|
||||
@@ -587,7 +587,7 @@ public class MoGoLiveManager {
|
||||
* 停止推流到ZeGo
|
||||
*/
|
||||
public void stopPublish() {
|
||||
Logger.i(TAG, "停止推流到ZeGo stopPublish currentStreamId : " + mLiveStatusModel.getCurrentStreamId());
|
||||
Log.i(TAG, "停止推流到ZeGo stopPublish currentStreamId : " + mLiveStatusModel.getCurrentStreamId());
|
||||
if (mExpressEngine == null) {
|
||||
return;
|
||||
}
|
||||
@@ -601,14 +601,14 @@ public class MoGoLiveManager {
|
||||
* @param surfaceView 渲染直播的视图
|
||||
*/
|
||||
public void startLive(SurfaceView surfaceView) {
|
||||
Logger.i(TAG, "尝试开始播放直播 startLive multiStreamId : " + mLiveStatusModel.getMultiStreamId());
|
||||
Log.i(TAG, "尝试开始播放直播 startLive multiStreamId : " + mLiveStatusModel.getMultiStreamId());
|
||||
if (!TextUtils.isEmpty(mLiveStatusModel.getMultiStreamId()) &&
|
||||
mLiveStatusModel.getMultiStreamId().startsWith(STREAM_ID_PREFIX)) {
|
||||
ZegoCanvas zegoCanvas = new ZegoCanvas(surfaceView);
|
||||
zegoCanvas.viewMode = ZegoViewMode.SCALE_TO_FILL;
|
||||
mExpressEngine.startPlayingStream(mLiveStatusModel.getMultiStreamId(), zegoCanvas);
|
||||
} else {
|
||||
Logger.e(TAG, "直播ID有误,请重试");
|
||||
Log.e(TAG, "直播ID有误,请重试");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,9 +616,9 @@ public class MoGoLiveManager {
|
||||
* 停止播放直播
|
||||
*/
|
||||
public void stopLive() {
|
||||
Logger.i(TAG, "尝试停止播放直播 stopLive multiStreamId : " + mLiveStatusModel.getMultiStreamId());
|
||||
Log.i(TAG, "尝试停止播放直播 stopLive multiStreamId : " + mLiveStatusModel.getMultiStreamId());
|
||||
if (mExpressEngine == null) {
|
||||
Logger.w(TAG, "ZeGo 引擎未初始化");
|
||||
Log.w(TAG, "ZeGo 引擎未初始化");
|
||||
return;
|
||||
}
|
||||
// 停止播放直播流
|
||||
@@ -634,7 +634,7 @@ public class MoGoLiveManager {
|
||||
private void destroyEngine() {
|
||||
mExpressEngine.enableCustomVideoCapture(false, customVideoCaptureConfig, ZegoPublishChannel.MAIN);
|
||||
ZegoExpressEngine.destroyEngine(() -> {
|
||||
Logger.i(TAG, "销毁ZeGo引擎");
|
||||
Log.i(TAG, "销毁ZeGo引擎");
|
||||
mLiveStatusModel.setExpressEngineCanUse(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,22 +36,22 @@ PASSWORD=xintai2018
|
||||
RELEASE=true
|
||||
# AI CLOUD 云平台
|
||||
# 工具类
|
||||
MOGO_UTILS_VERSION=1.1.49-live
|
||||
MOGO_UTILS_VERSION=1.1.53-live
|
||||
# 网络请求
|
||||
MOGO_NETWORK_VERSION=1.1.49-live
|
||||
MOGO_NETWORK_VERSION=1.1.53-live
|
||||
# 网络DNS
|
||||
MOGO_HTTPDNS_VERSION=1.1.49-live
|
||||
MOGO_HTTPDNS_VERSION=1.1.53-live
|
||||
# 鉴权
|
||||
MOGO_PASSPORT_VERSION=1.1.49-live
|
||||
MOGO_PASSPORT_VERSION=1.1.53-live
|
||||
# 常链接
|
||||
MOGO_SOCKET_VERSION=1.1.49-live
|
||||
MOGO_SOCKET_VERSION=1.1.53-live
|
||||
# 数据采集
|
||||
MOGO_REALTIME_VERSION=1.1.49-live
|
||||
MOGO_REALTIME_VERSION=1.1.53-live
|
||||
# 探路,道路事件发布,获取
|
||||
MOGO_TANLU_VERSION=1.1.49-live
|
||||
MOGO_TANLU_VERSION=1.1.53-live
|
||||
# 直播推流
|
||||
MOGO_LIVE_VERSION=1.1.49-live
|
||||
MOGO_LIVE_VERSION=1.1.53-live
|
||||
# 直播拉流
|
||||
MOGO_TRAFFICLIVE_VERSION=1.1.49-live
|
||||
MOGO_TRAFFICLIVE_VERSION=1.1.53-live
|
||||
# 定位服务
|
||||
MOGO_LOCATION_VERSION=1.1.49-live
|
||||
MOGO_LOCATION_VERSION=1.1.53-live
|
||||
|
||||
Reference in New Issue
Block a user