Commit b8338b29 by mReturn

接入直播

parent 81caa6a4
Showing with 4502 additions and 51 deletions
......@@ -23,7 +23,7 @@ android {
}
}
ndk {
abiFilters 'x86', 'armeabi-v7a','armeabi'
abiFilters 'armeabi-v7a','armeabi'
}
}
......
......@@ -53,15 +53,15 @@
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:label,icon,theme,allowBackup">
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.dayu.bigfish.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<!--<provider-->
<!--android:name="android.support.v4.content.FileProvider"-->
<!--android:authorities="com.dayu.bigfish.fileProvider"-->
<!--android:exported="false"-->
<!--android:grantUriPermissions="true">-->
<!--<meta-data-->
<!--android:name="android.support.FILE_PROVIDER_PATHS"-->
<!--android:resource="@xml/file_paths" />-->
<!--</provider>-->
<!-- <provider -->
<!-- android:name="android.support.v4.content.FileProvider" -->
......
......@@ -16,13 +16,13 @@ import com.dayu.bigfish.ui.service.LocationService;
import com.dayu.bigfish.utils.HxManager;
import com.dayu.common.BaseApplication;
import com.dayu.common.Constants;
import com.dayu.livemodule.LiveUtils;
import com.dayu.location.base.LocationUtils;
import com.dayu.order.greendao.GreenDaoManager;
import com.dayu.order.ui.activity.OrderDetailsActivity;
import com.dayu.utils.LogUtils;
import com.dayu.utils.NetworkConnectChangedReceiver;
import com.dayu.utils.SPUtils;
import com.dayu.utils.ToastUtils;
import com.dayu.utils.UserManager;
import com.scwang.smartrefresh.header.MaterialHeader;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
......@@ -91,6 +91,9 @@ public class MyApplication extends BaseApplication {
//初始化环信
HxManager.getInstance().init(mContext);
//直播
LiveUtils.initLive(this);
}
//初始化刷新框架 static 代码段可以防止内存泄露
......
......@@ -14,15 +14,11 @@ import android.support.v4.app.NotificationCompat;
import android.support.v4.content.FileProvider;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.alibaba.android.arouter.launcher.ARouter;
import com.dayu.event.LearnTabNumEvent;
import com.dayu.event.SaleTabNumEvent;
import com.bigfish.salecenter.ui.fragment.HomeSaleFragment;
import com.dayu.base.api.Api;
import com.dayu.base.api.DownloadService;
......@@ -42,8 +38,11 @@ import com.dayu.bigfish.utils.HxManager;
import com.dayu.common.BaseApplication;
import com.dayu.common.Constants;
import com.dayu.event.DownloadBean;
import com.dayu.event.LearnTabNumEvent;
import com.dayu.event.SaleTabNumEvent;
import com.dayu.event.UserInfo;
import com.dayu.learncenter.ui.fragment.HomeLearnFragment;
import com.dayu.livemodule.LiveUtils;
import com.dayu.location.base.LocationUtils;
import com.dayu.order.common.TabNumEvent;
import com.dayu.order.ui.activity.OrderDetailsActivity;
......@@ -113,6 +112,7 @@ public class MainActivity extends BaseActivity<MainPresenter, ActivityMainBindin
MobclickAgent.onEvent(BaseApplication.getContext(), "push_offline_check_order_detail");
}
initUser();
LiveUtils.initUser(mUserId,mUserInfo.getAccountName(),mUserInfo.getHeaderImg());
MobclickAgent.onEvent(this, "go_home");
saleFragment = HomeSaleFragment.newInstance();
secondFragment = HomeOrderFragment.newInstance();
......
......@@ -37,18 +37,18 @@ public class Constants {
/**
* dev环境配置.
*/
// public static String ENVIROMENT = "debug";
// public static final int LOG_LEVEL = LogUtils.LEVEL_ALL;
// public static String BASE_URL = "http://47.94.101.239:3112";
// public static String WEB_SOP = "http://47.94.101.239:9004/#/sop";
// public static String CHECK_MULTI_WEB_SOP = "http://47.94.101.239:9004/#/manyServiceResult";
// public static String MULTI_WEB_SOP = "http://47.94.101.239:9004/#/manySop";
// public static String WEB_SOP_DETAIL = "http://47.94.101.239:9004/#/sopdetail";
// public static String WEB_ZHI_SHI = "http://47.94.101.239:9004/#/detail";
// public static final String UP_PHOTO = "/file/uploadMore?targetPath=test/sp/mobile/android/business/checkApply";
// public static final String UP_VIDEO = "/file/uploadVideoOne?targetPath=dev/video";
// public static final boolean IS_DEBUG = true;
// public static final boolean CAN_CHANGE_ENV = true; //是否显示切换开发环境菜单
public static String ENVIROMENT = "debug";
public static final int LOG_LEVEL = LogUtils.LEVEL_ALL;
public static String BASE_URL = "http://47.94.101.239:3112";
public static String WEB_SOP = "http://47.94.101.239:9004/#/sop";
public static String CHECK_MULTI_WEB_SOP = "http://47.94.101.239:9004/#/manyServiceResult";
public static String MULTI_WEB_SOP = "http://47.94.101.239:9004/#/manySop";
public static String WEB_SOP_DETAIL = "http://47.94.101.239:9004/#/sopdetail";
public static String WEB_ZHI_SHI = "http://47.94.101.239:9004/#/detail";
public static final String UP_PHOTO = "/file/uploadMore?targetPath=test/sp/mobile/android/business/checkApply";
public static final String UP_VIDEO = "/file/uploadVideoOne?targetPath=dev/video";
public static final boolean IS_DEBUG = true;
public static final boolean CAN_CHANGE_ENV = true; //是否显示切换开发环境菜单
/**
......@@ -70,18 +70,18 @@ public class Constants {
/**
* 正式环境.
*/
public static String ENVIROMENT = "release";
public static int LOG_LEVEL = LogUtils.LEVEL_OFF;
public static String BASE_URL = "https://mobile.kf.ai";
public static String WEB_SOP = "https://sop.kf.ai/#/sop";
public static String WEB_SOP_DETAIL = "https://sop.kf.ai/#/sopdetail";
public static String WEB_ZHI_SHI = "https://sop.kf.ai/#/detail";
public static String CHECK_MULTI_WEB_SOP = "https://sop.kf.ai/#/manyServiceResult";
public static String MULTI_WEB_SOP = "https://sop.kf.ai/#/manySop";
public static final String UP_PHOTO = "/file/uploadMore?targetPath=online/sp/mobile/android/business/checkApply";
public static final String UP_VIDEO = "/file/uploadVideoOne?targetPath=online/video";
public static final boolean IS_DEBUG = false;
public static final boolean CAN_CHANGE_ENV = false; //是否显示切换开发环境菜单
// public static String ENVIROMENT = "release";
// public static int LOG_LEVEL = LogUtils.LEVEL_OFF;
// public static String BASE_URL = "https://mobile.kf.ai";
// public static String WEB_SOP = "https://sop.kf.ai/#/sop";
// public static String WEB_SOP_DETAIL = "https://sop.kf.ai/#/sopdetail";
// public static String WEB_ZHI_SHI = "https://sop.kf.ai/#/detail";
// public static String CHECK_MULTI_WEB_SOP = "https://sop.kf.ai/#/manyServiceResult";
// public static String MULTI_WEB_SOP = "https://sop.kf.ai/#/manySop";
// public static final String UP_PHOTO = "/file/uploadMore?targetPath=online/sp/mobile/android/business/checkApply";
// public static final String UP_VIDEO = "/file/uploadVideoOne?targetPath=online/video";
// public static final boolean IS_DEBUG = false;
// public static final boolean CAN_CHANGE_ENV = false; //是否显示切换开发环境菜单
/**
* 统一配置.
......
......@@ -5,14 +5,11 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.dayu.baselibrary.R;
import com.dayu.common.BaseApplication;
......@@ -24,6 +21,7 @@ import com.umeng.socialize.UMShareListener;
import com.umeng.socialize.bean.SHARE_MEDIA;
import com.umeng.socialize.media.UMImage;
import com.umeng.socialize.media.UMMin;
import com.umeng.socialize.media.UMVideo;
import com.umeng.socialize.media.UMWeb;
import java.text.ParseException;
......@@ -297,6 +295,20 @@ public class CommonUtils {
// .setPlatform(SHARE_MEDIA.WEIXIN_CIRCLE)
// .setCallback(callBack).share();
}
/**
* 微信(朋友圈)分享链接(包含标题内容)
*/
public static void shareWxVideo(Activity activity, String videoUrl, String imgUrl, String title,
String description, UMShareListener callBack) {
UMVideo video = new UMVideo(videoUrl);
video.setTitle(title);//视频的标题
video.setDescription(description);//视频的描述
new ShareAction(activity).withMedia(video)
.setDisplayList(SHARE_MEDIA.WEIXIN, SHARE_MEDIA.WEIXIN_CIRCLE)
.setCallback(callBack).open();
// .setPlatform(SHARE_MEDIA.WEIXIN_CIRCLE)
// .setCallback(callBack).share();
}
......
......@@ -19,7 +19,6 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
......@@ -50,8 +49,8 @@ public class GlideImageLoader {
public static void load(Context context,ImageView view, String imageRes, int defaultRes) {
RequestOptions options = new RequestOptions()
.centerCrop()
.placeholder(defaultRes)
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL);
Glide.with(context)
.load(imageRes)
......@@ -69,6 +68,15 @@ public class GlideImageLoader {
.apply(options)
.into(view);
}
public static void loadNoDeal(Context context, ImageView view, String imageRes, int defaultRes) {
RequestOptions options = new RequestOptions()
.placeholder(defaultRes)
.diskCacheStrategy(DiskCacheStrategy.ALL);
Glide.with(context)
.load(imageRes)
.apply(options)
.into(view);
}
public static void loadDrawable(Context context, String imageRes, ImageView view) {
RequestOptions options = new RequestOptions()
.centerCrop()
......
......@@ -19,6 +19,13 @@ public class MediaChooseUtils {
public static void chooseSigleImg(Activity activity) {
chooseMedia(activity,PictureMimeType.ofImage(),1,0);
}
public static void chooseCropSigleImg(Activity activity) {
PictureSelectionModel selector = getPictureSelectionModel(activity, PictureMimeType.ofImage(), 1, 0);
selector.enableCrop(true)//是否裁剪
.withAspectRatio(1, 1)
.freeStyleCropEnabled(true);
selector.forResult(PictureConfig.CHOOSE_REQUEST);
}
/**
* 选择头像图片并裁剪
......
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="15dp"/>
<solid android:color="@color/cl_tab_gray"/>
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="15dp"/>
<solid android:color="@color/dark_red"/>
</shape>
\ No newline at end of file
......@@ -997,6 +997,7 @@ C) 在甲方使用大鱼平台服务过程中产生的业务数据,客户数
<item>公开课</item>
<item>已学习</item>
<item>已发布</item>
<item>直播</item>
</array>
<string name="pub_course_title">我要开课</string>
<string name="course_theme">课程主题:</string>
......
......@@ -3,7 +3,7 @@
buildscript {
ext.compile_sdk_version = 27
ext.build_tools_version = "27.0.3"
ext.min_sdk_version = 16
ext.min_sdk_version = 17
ext.target_sdk_version = 26
ext.version_code = 255
ext.verson_name = "2.5.5"
......
......@@ -61,4 +61,6 @@ dependencies {
//ARouter
annotationProcessor "com.alibaba:arouter-compiler:$arouter_compiler_version"
api project(':provider')
api project(':liveModule')
}
......@@ -8,7 +8,6 @@ import com.dayu.learncenter.api.bean.CommonLearnBean;
import com.dayu.learncenter.databinding.ItemCommonLearnBinding;
import com.dayu.learncenter.presenter.common_learn.CommonLearnPresenter;
import com.dayu.utils.CommonUtils;
import com.dayu.utils.LogUtils;
import com.dayu.widgets.JZMediaIjk;
import com.dayu.widgets.MyJzvdStd;
import com.umeng.analytics.MobclickAgent;
......@@ -35,6 +34,7 @@ public class LearnAdapter extends CoreAdapter<CommonLearnBean, ItemCommonLearnBi
holder.tvLooks.setText(item.getLearners() + "");
holder.tvLike.setText(item.getPoints() + "");
holder.tvDate.setText("发布时间 " + CommonUtils.getYearData(item.getCreateTime()));
holder.ibShare.setOnClickListener(view -> mPresenter.shareVideo(item));
if (type == 2) {
holder.tvProgress.setVisibility(View.VISIBLE);
holder.tvProgress.setText(mContext.getString(R.string.learn_progress) + item.getProgressPercentage() + "%");
......
......@@ -9,12 +9,12 @@ import com.dayu.common.Constants;
import com.dayu.learncenter.api.bean.CommonLearnBean;
import com.dayu.learncenter.api.bean.CourseDeatilBean;
import com.dayu.learncenter.api.bean.LearnTabBean;
import com.dayu.learncenter.api.bean.LiveVideosBean;
import com.dayu.learncenter.api.data.EditCourseData;
import com.dayu.learncenter.api.data.LiveData;
import com.dayu.learncenter.api.data.PubCourseData;
import com.dayu.learncenter.api.data.StudyCourseData;
import java.security.Key;
import io.reactivex.Observable;
import retrofit2.http.Body;
import retrofit2.http.GET;
......@@ -134,4 +134,25 @@ public interface LearnService {
*/
@POST(Constants.API_7400 + "/leaveMessage/courses/engineer")
Observable<BaseResponse<Boolean>> addCommentReply(@Body CommentReplyData data);
/**
* 上传开播信息
*/
@POST(Constants.API_7900 + "/live/signType")
Observable<BaseResponse<Boolean>> sendLiveData(@Body LiveData data);
/**
* 获取直播视频列表
* @param liveStatus 1.直播中 2.已停止
* @param status 1.正常 2.删除
* @param liveStreamType 1.主播 2.连麦
* @return
*/
@GET(Constants.API_7900 + "/live")
Observable<BaseResponse<BasePageBean<LiveVideosBean>>> getLiveVideos(@Query("liveStatus") int liveStatus,
@Query("status") int status,
@Query("liveStreamType") int liveStreamType,
@Query("page") int page,
@Query("pageSize") int pageSize);
}
package com.dayu.learncenter.api.bean;
public class LiveVideosBean {
/**
* id : 33
* title : null
* description : null
* startTime : 2020-06-12 17:05:31
* endTime : 2020-06-12 17:06:19
* createTime : 2020-06-12 17:05:31
* created : 1445
* watchNum : null
* playBackNum : null
* msgNum : null
* pointNum : null
* liveStreamName : 1400377925_1445
* liveExpireTime : null
* liveAppName : live
* livePushUrl : txSecret=467df588304e43cf40dccf7fd6a40ca5&txTime=5EE496D9
* liveStatus : 2
* videoUrl : http://1301600814.vod2.myqcloud.com/de97e17avodcq1301600814/b4fd0f4c5285890804066583604/f0.mp4
* liveSequence : 9561148648144146113
* liveStreamType : 1
* status : 1
* liveErrCode : 1
* liveErrMsg : recv RTMP deleteStream command
* liveStreamPic : null
*/
private int id;
private String title;
private Object description;
private String startTime;
private String endTime;
private String createTime;
private String created;
private Object watchNum;
private int playBackNum;
private Object msgNum;
private int pointNum;
private String liveStreamName;
private Object liveExpireTime;
private String liveAppName;
private String livePushUrl;
private int liveStatus;
private String videoUrl;
private String liveSequence;
private int liveStreamType;
private int status;
private int liveErrCode;
private String liveErrMsg;
private Object liveStreamPic;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Object getDescription() {
return description;
}
public void setDescription(Object description) {
this.description = description;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getCreated() {
return created;
}
public void setCreated(String created) {
this.created = created;
}
public Object getWatchNum() {
return watchNum;
}
public void setWatchNum(Object watchNum) {
this.watchNum = watchNum;
}
public int getPlayBackNum() {
return playBackNum;
}
public void setPlayBackNum(int playBackNum) {
this.playBackNum = playBackNum;
}
public Object getMsgNum() {
return msgNum;
}
public void setMsgNum(Object msgNum) {
this.msgNum = msgNum;
}
public int getPointNum() {
return pointNum;
}
public void setPointNum(int pointNum) {
this.pointNum = pointNum;
}
public String getLiveStreamName() {
return liveStreamName;
}
public void setLiveStreamName(String liveStreamName) {
this.liveStreamName = liveStreamName;
}
public Object getLiveExpireTime() {
return liveExpireTime;
}
public void setLiveExpireTime(Object liveExpireTime) {
this.liveExpireTime = liveExpireTime;
}
public String getLiveAppName() {
return liveAppName;
}
public void setLiveAppName(String liveAppName) {
this.liveAppName = liveAppName;
}
public String getLivePushUrl() {
return livePushUrl;
}
public void setLivePushUrl(String livePushUrl) {
this.livePushUrl = livePushUrl;
}
public int getLiveStatus() {
return liveStatus;
}
public void setLiveStatus(int liveStatus) {
this.liveStatus = liveStatus;
}
public String getVideoUrl() {
return videoUrl;
}
public void setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
}
public String getLiveSequence() {
return liveSequence;
}
public void setLiveSequence(String liveSequence) {
this.liveSequence = liveSequence;
}
public int getLiveStreamType() {
return liveStreamType;
}
public void setLiveStreamType(int liveStreamType) {
this.liveStreamType = liveStreamType;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public int getLiveErrCode() {
return liveErrCode;
}
public void setLiveErrCode(int liveErrCode) {
this.liveErrCode = liveErrCode;
}
public String getLiveErrMsg() {
return liveErrMsg;
}
public void setLiveErrMsg(String liveErrMsg) {
this.liveErrMsg = liveErrMsg;
}
public Object getLiveStreamPic() {
return liveStreamPic;
}
public void setLiveStreamPic(Object liveStreamPic) {
this.liveStreamPic = liveStreamPic;
}
}
package com.dayu.learncenter.api.data;
public class LiveData {
public int liveStreamType;
public String pushUrl;
public LiveData(int liveStreamType, String pushUrl) {
this.liveStreamType = liveStreamType;
this.pushUrl = pushUrl;
}
}
......@@ -2,9 +2,11 @@ package com.dayu.learncenter.presenter.common_learn;
import com.dayu.base.ui.presenter.BaseListPresenter;
import com.dayu.common.BaseView;
import com.dayu.learncenter.api.bean.CommonLearnBean;
public interface CommonLearnContract {
interface View extends BaseView {
void shareVideo(CommonLearnBean item);
}
abstract class Presenter extends BaseListPresenter<View> {
......
......@@ -99,4 +99,8 @@ public class CommonLearnPresenter extends CommonLearnContract.Presenter {
}));
}
public void shareVideo(CommonLearnBean item) {
mView.shareVideo(item);
}
}
package com.dayu.learncenter.ui.activity;
import android.content.Intent;
import android.text.TextUtils;
import android.view.View;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.dayu.base.api.BaseApiFactory;
import com.dayu.base.ui.activity.BaseActivity;
import com.dayu.base.ui.presenter.SImplePresenter;
import com.dayu.learncenter.R;
import com.dayu.learncenter.databinding.ActivityPrepareLiveBinding;
import com.dayu.livemodule.xiaozhibo.anchor.TCCameraAnchorActivity;
import com.dayu.livemodule.xiaozhibo.common.utils.TCConstants;
import com.dayu.livemodule.xiaozhibo.login.TCUserMgr;
import com.dayu.utils.MediaChooseUtils;
import com.luck.picture.lib.PictureSelector;
import com.luck.picture.lib.config.PictureConfig;
import com.luck.picture.lib.entity.LocalMedia;
import java.io.File;
import java.util.List;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
public class PrepareLiveActivity extends BaseActivity<SImplePresenter, ActivityPrepareLiveBinding> {
String title;
@Override
public void setPresenter() {
}
@Override
public int getLayoutId() {
return R.layout.activity_prepare_live;
}
@Override
public void initView() {
mBind.titleBack.setOnClickListener(view -> dumpBack());
mBind.ivCover.setOnClickListener(view -> {
MediaChooseUtils.chooseCropSigleImg(this);
});
mBind.btnConfirm.setOnClickListener(v->{
title = mBind.edtTitle.getText().toString().trim();
if (TextUtils.isEmpty(title)){
showToast("请输入标题");
}else {
startPublish();
}
});
initCover();
}
/**
* 初始化封面图
*/
private void initCover() {
String strCover = TCUserMgr.getInstance().getCoverPic();
if (!TextUtils.isEmpty(strCover)) {
RequestManager req = Glide.with(this);
req.load(strCover).into(mBind.ivCover);
mBind.tvTips.setVisibility(View.GONE);
} else {
mBind.ivCover.setImageResource(com.dayu.livemodule.R.drawable.publish_background);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case PictureConfig.CHOOSE_REQUEST:
List<LocalMedia> mSelectList = PictureSelector.obtainMultipleResult(data);
if (mSelectList != null) {
for (int a = 0; a < mSelectList.size(); a++) {
uploadPic(mSelectList.get(a).getCutPath());
}
}
break;
}
}
}
//上传图片
private void uploadPic(String path) {
File file = new File(path);
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("fileUpload", file.getName(), requestFile);
showDialog();
BaseApiFactory.uploadPhoto(body).subscribe(mPresenter.baseObserver(list -> {
if (list != null && list.size() > 0) {
TCUserMgr.getInstance().setCoverPic(list.get(0), null);
initCover();
} else {
showToast("图片上传失败");
}
}));
}
private void startPublish() {
Intent intent = new Intent(this, TCCameraAnchorActivity.class);
intent.putExtra(TCConstants.ROOM_TITLE,title);
intent.putExtra(TCConstants.USER_ID, TCUserMgr.getInstance().getUserId());
intent.putExtra(TCConstants.USER_NICK, TCUserMgr.getInstance().getNickname());
intent.putExtra(TCConstants.USER_HEADPIC, TCUserMgr.getInstance().getAvatar());
intent.putExtra(TCConstants.COVER_PIC, TCUserMgr.getInstance().getCoverPic());
intent.putExtra(TCConstants.USER_LOC,"");
startActivity(intent);
finish();
}
}
......@@ -12,6 +12,7 @@ import com.dayu.common.Constants;
import com.dayu.common.MyTextWatcher;
import com.dayu.learncenter.R;
import com.dayu.learncenter.adapter.LearnAdapter;
import com.dayu.learncenter.api.bean.CommonLearnBean;
import com.dayu.learncenter.databinding.FragmentCommonLearnBinding;
import com.dayu.learncenter.event.CourseModifyEvent;
import com.dayu.learncenter.event.CoursePubEvent;
......@@ -161,4 +162,9 @@ public class CommonLearnFragment extends BaseFragment<CommonLearnPresenter, Frag
public void onModify(CourseModifyEvent event) {
mPresenter.refresh();
}
@Override
public void shareVideo(CommonLearnBean item) {
CommonUtils.shareWxVideo(mActivity,item.getUrl(),"",item.getName(),item.getBrief(),null);
}
}
......@@ -15,13 +15,16 @@ import com.dayu.base.ui.presenter.SImplePresenter;
import com.dayu.learncenter.R;
import com.dayu.learncenter.api.LearnService;
import com.dayu.learncenter.api.bean.LearnTabBean;
import com.dayu.learncenter.api.data.LiveData;
import com.dayu.learncenter.databinding.FragmentHomeLearnBinding;
import com.dayu.learncenter.event.RefreshLeanTabEvent;
import com.dayu.learncenter.ui.activity.PubCourseActivity;
import com.dayu.learncenter.ui.activity.PrepareLiveActivity;
import com.dayu.livemodule.StartPushEvent;
import com.dayu.livemodule.xiaozhibo.anchor.prepare.TCAnchorPrepareActivity;
import com.dayu.utils.TabLayoutUtils;
import com.dayu.utils.ToastUtils;
import com.dayu.utils.UIUtils;
import com.umeng.analytics.MobclickAgent;
import com.youth.banner.WeakHandler;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
......@@ -39,6 +42,7 @@ public class HomeLearnFragment extends BaseFragment<SImplePresenter, FragmentHom
private boolean isFirstAddTab = true;
private String[] tabDesc;
private LearnTabBean tabBean;
WeakHandler weakHandler = new WeakHandler();
@Override
public void setPresenter() {
......@@ -53,7 +57,8 @@ public class HomeLearnFragment extends BaseFragment<SImplePresenter, FragmentHom
public void initView() {
initUser();
tabDesc = UIUtils.getStrings(R.array.learn_top_tab_item);
mBind.ivPubCourse.setOnClickListener(view -> startActivity(PubCourseActivity.class));
mBind.ivPubCourse.setOnClickListener(view -> startActivity(TCAnchorPrepareActivity.class));
mBind.ivPubCourse.setOnClickListener(view -> startActivity(PrepareLiveActivity.class));
initTabPage();
getTabData();
EventBus.getDefault().register(this);
......@@ -74,6 +79,8 @@ public class HomeLearnFragment extends BaseFragment<SImplePresenter, FragmentHom
list_fragments.add(CommonLearnFragment.newInstance(1));
list_fragments.add(CommonLearnFragment.newInstance(2));
list_fragments.add(CommonLearnFragment.newInstance(3));
// list_fragments.add(new TCVideoListFragment());
list_fragments.add(new LiveListFragment());
adapter = new FragmentAdapter(getFragmentManager(), list_fragments);
mBind.vpLearn.setAdapter(adapter);
mBind.tabLearn.setupWithViewPager(mBind.vpLearn);
......@@ -129,6 +136,7 @@ public class HomeLearnFragment extends BaseFragment<SImplePresenter, FragmentHom
mBind.tabLearn.addTab(mBind.tabLearn.newTab().setCustomView(CreatTab(0, tabNum[0], tabDesc[0])));
mBind.tabLearn.addTab(mBind.tabLearn.newTab().setCustomView(CreatTab(1, tabNum[1], tabDesc[1])));
mBind.tabLearn.addTab(mBind.tabLearn.newTab().setCustomView(CreatTab(2, tabNum[2], tabDesc[2])));
mBind.tabLearn.addTab(mBind.tabLearn.newTab().setCustomView(CreatTab(3, 0, tabDesc[3])));
isFirstAddTab = false;
} else {
for (int i = 0; i < mBind.tabLearn.getTabCount(); i++) {
......@@ -165,4 +173,20 @@ public class HomeLearnFragment extends BaseFragment<SImplePresenter, FragmentHom
public void refreTab(RefreshLeanTabEvent event) {
getTabData();
}
@Subscribe
public void startPush(StartPushEvent event) {
sendLiveData(event);
weakHandler.postDelayed(() -> {
sendLiveData(event);
},10000);
}
private void sendLiveData(StartPushEvent event) {
int type = event.isMain?1:2;
LiveData liveData = new LiveData(type,event.pushUrl);
Api.getService(LearnService.class).sendLiveData(liveData).compose(Api.applySchedulers())
.subscribe(mPresenter.baseObserver(result->{}));
}
}
package com.dayu.learncenter.ui.fragment;
import android.content.Intent;
import android.support.v7.widget.LinearLayoutManager;
import android.text.TextUtils;
import android.widget.ImageView;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.dayu.base.api.Api;
import com.dayu.base.ui.fragment.BaseFragment;
import com.dayu.base.ui.presenter.SImplePresenter;
import com.dayu.common.Constants;
import com.dayu.learncenter.R;
import com.dayu.learncenter.api.LearnService;
import com.dayu.learncenter.api.bean.LiveVideosBean;
import com.dayu.learncenter.databinding.FragmentLiveListBinding;
import com.dayu.livemodule.xiaozhibo.audience.TCAudienceActivity;
import com.dayu.livemodule.xiaozhibo.common.utils.TCConstants;
import com.dayu.livemodule.xiaozhibo.main.videolist.utils.TCVideoInfo;
import com.dayu.livemodule.xiaozhibo.main.videolist.utils.TCVideoListMgr;
import com.dayu.utils.CommonUtils;
import com.dayu.utils.GlideImageLoader;
import com.dayu.widgets.JZMediaIjk;
import com.dayu.widgets.MyJzvdStd;
import java.util.ArrayList;
import java.util.List;
import cn.jzvd.JzvdStd;
public class LiveListFragment extends BaseFragment<SImplePresenter, FragmentLiveListBinding> {
List<TCVideoInfo> liveList = new ArrayList<>();
List<LiveVideosBean> playBackList = new ArrayList<>();
BaseQuickAdapter<LiveVideosBean, BaseViewHolder> playBackAdapter;
int mPage = 1;
@Override
public void setPresenter() {
}
@Override
public int getLayoutId() {
return R.layout.fragment_live_list;
}
@Override
public void initView() {
mBind.rvLive.setNestedScrollingEnabled(false);
mBind.rvVideo.setNestedScrollingEnabled(false);
mBind.refreshLayout.setOnRefreshListener(refreshLayout -> {
mPage =1;
initData();
});
mBind.refreshLayout.setOnLoadMoreListener(refreshLayout -> {
getPlayBackVideos();
});
}
@Override
protected void lazyLoad() {
super.lazyLoad();
initData();
}
private void initData() {
getLiveList();
getPlayBackVideos();
}
//直播列表
private void getLiveList() {
TCVideoListMgr.getInstance().fetchLiveList(getActivity(), new TCVideoListMgr.Listener() {
@Override
public void onVideoList(int retCode, ArrayList<TCVideoInfo> result, boolean refresh) {
mBind.refreshLayout.finishRefresh();
liveList.clear();
if (retCode == 0 && result != null)
liveList.addAll(result);
setLiveAdapter();
}
});
}
//回放列表
private void getPlayBackVideos() {
Api.getService(LearnService.class).getLiveVideos(2,1,1,mPage, Constants.PAGESIZE).compose(Api.applySchedulers())
.subscribe(mPresenter.baseObserver(data ->{
mBind.refreshLayout.finishRefresh();
mBind.refreshLayout.finishLoadMore();
if (mPage == 1) {
playBackList.clear();
}
playBackList.addAll(data.getData());
setPlaybackAdapter();
mBind.refreshLayout.setEnableLoadMore(mPage < data.getTotalPages());
mPage += 1;
}, responeThrowable -> {
mBind.refreshLayout.finishRefresh();
mBind.refreshLayout.finishLoadMore();
}));
}
private void setLiveAdapter() {
BaseQuickAdapter<TCVideoInfo, BaseViewHolder> liveAdapter = new BaseQuickAdapter
<TCVideoInfo, BaseViewHolder>(R.layout.item_video_live, liveList) {
@Override
protected void convert(BaseViewHolder helper, TCVideoInfo item) {
ImageView ivCover = helper.getView(R.id.iv_cover);
GlideImageLoader.load(mActivity, ivCover, item.frontCover, R.drawable.icon_video_default);
helper.setText(R.id.tv_title, item.title);
}
};
liveAdapter.setOnItemClickListener((adapter, view, position) -> {
startLivePlay(liveList.get(position));
});
mBind.rvLive.setLayoutManager(new LinearLayoutManager(mActivity));
mBind.rvLive.setAdapter(liveAdapter);
}
private void startLivePlay(final TCVideoInfo item) {
Intent intent;
intent = new Intent(mActivity, TCAudienceActivity.class);
intent.putExtra(TCConstants.PLAY_URL, item.playUrl);
intent.putExtra(TCConstants.PUSHER_ID, item.userId != null ? item.userId : "");
intent.putExtra(TCConstants.PUSHER_NAME, TextUtils.isEmpty(item.nickname) ? item.userId : item.nickname);
intent.putExtra(TCConstants.PUSHER_AVATAR, item.avatar);
intent.putExtra(TCConstants.HEART_COUNT, "" + item.likeCount);
intent.putExtra(TCConstants.MEMBER_COUNT, "" + item.viewerCount);
intent.putExtra(TCConstants.GROUP_ID, item.groupId);
intent.putExtra(TCConstants.PLAY_TYPE, item.livePlay);
intent.putExtra(TCConstants.FILE_ID, item.fileId != null ? item.fileId : "");
intent.putExtra(TCConstants.COVER_PIC, item.frontCover);
intent.putExtra(TCConstants.TIMESTAMP, item.createTime);
intent.putExtra(TCConstants.ROOM_TITLE, item.title);
mActivity.startActivity(intent);
}
private void setPlaybackAdapter() {
if (playBackAdapter != null){
playBackAdapter.notifyDataSetChanged();
}else{
playBackAdapter = new BaseQuickAdapter<LiveVideosBean, BaseViewHolder>(R.layout.item_live_play_back, playBackList) {
@Override
protected void convert(BaseViewHolder helper, LiveVideosBean item) {
helper.setText(R.id.tv_title, item.getTitle());
MyJzvdStd jzVideo = helper.getView(R.id.jz_video);
jzVideo.setUp(item.getVideoUrl(), "", JzvdStd.SCREEN_NORMAL, JZMediaIjk.class);
CommonUtils.setVideoThumb(mContext, jzVideo, item.getVideoUrl());
}
};
mBind.rvVideo.setLayoutManager(new LinearLayoutManager(mActivity));
mBind.rvVideo.setAdapter(playBackAdapter);
}
}
}
......@@ -16,6 +16,9 @@
android:configChanges="orientation|screenSize|keyboardHidden"
android:screenOrientation="portrait"
/>
<activity android:name=".ui.activity.PrepareLiveActivity"
android:screenOrientation="portrait"
/>
</application>
</manifest>
......
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/cl_bg"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/rl_title"
style="@style/title">
<TextView
android:id="@+id/tv_title"
style="@style/text_title"
android:text="发布直播" />
<ImageView
android:id="@+id/title_back"
style="@style/title_image_back" />
<ImageView
style="@style/card_line"
android:layout_alignParentBottom="true" />
</RelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_cover"
android:layout_width="match_parent"
android:layout_height="250dp"
android:onClick="onClick"
android:scaleType="fitXY" />
<TextView
android:id="@+id/tv_tips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableTop="@drawable/image"
android:drawablePadding="5dp"
android:gravity="center"
android:text="@string/text_live_add_title_tips"
android:textColor="@color/colorTextG2"
android:textSize="16sp" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/edt_title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/white"
android:gravity="top"
android:hint="@string/text_live_title_input"
android:maxLength="32"
android:maxLines="1"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:singleLine="true"
android:textColor="@color/colorTextG4"
android:textColorHint="@color/colorTextG2"
android:textSize="@dimen/h6" />
</FrameLayout>
<View
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_confirm"
style="@style/btn_common_blue"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_weight="1.5"
android:text="开始直播" />
</LinearLayout>
</LinearLayout>
</layout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f5f5f5">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_live"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never" />
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
</layout>
\ No newline at end of file
......@@ -105,6 +105,13 @@
android:layout_marginLeft="5dp"
android:layout_marginRight="10dp"
android:text="0" />
<ImageButton
android:id="@+id/ib_share"
android:layout_width="40dp"
android:background="@color/transparent"
android:layout_height="match_parent"
android:src="@drawable/icon_share_gray" />
</LinearLayout>
</LinearLayout>
......
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_13.3"
android:layout_marginTop="5dp"
android:layout_marginRight="@dimen/dp_13.3"
android:background="@drawable/item_shape"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/rl_video"
android:layout_width="match_parent"
android:layout_height="180dp">
<com.dayu.widgets.MyJzvdStd
android:id="@+id/jz_video"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:id="@+id/rl_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent">
<TextView
android:id="@+id/tv_progress"
style="@style/common_text_style"
android:layout_centerHorizontal="true"
android:layout_marginTop="15dp"
android:padding="10dp"
android:text="@string/learn_progress"
android:textColor="@color/text_common_blue"
android:textSize="20sp"
android:visibility="gone" />
<TextView
android:id="@+id/tv_disable"
style="@style/common_text_style"
android:layout_width="100dp"
android:layout_alignParentRight="true"
android:height="36dp"
android:background="@drawable/bg_live_gray_react"
android:gravity="center"
android:layout_margin="5dp"
android:text="回放视频"
android:textColor="@color/white" />
</RelativeLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_title"
style="@style/common_text_style"
android:layout_width="0dp"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:maxLines="2"
android:text="标题" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:src="@drawable/icon_looks" />
<TextView
android:id="@+id/tv_looks"
style="@style/common_text_style"
android:layout_marginLeft="5dp"
android:text="0" />
<ImageView
android:id="@+id/iv_like"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginLeft="10dp"
android:src="@drawable/icon_like_gray" />
<TextView
android:id="@+id/tv_like"
style="@style/common_text_style"
android:layout_marginLeft="5dp"
android:layout_marginRight="10dp"
android:text="0" />
<ImageButton
android:id="@+id/ib_share"
android:layout_width="40dp"
android:background="@color/transparent"
android:layout_height="match_parent"
android:src="@drawable/icon_share_gray" />
</LinearLayout>
</LinearLayout>
</layout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_13.3"
android:layout_marginTop="5dp"
android:layout_marginRight="@dimen/dp_13.3"
android:background="@drawable/item_shape"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/rl_video"
android:layout_width="match_parent"
android:layout_height="180dp">
<ImageView
android:id="@+id/iv_cover"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:id="@+id/rl_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent">
<ImageView
android:layout_width="45dp"
android:layout_height="45dp"
android:src="@drawable/jz_play_normal"
android:layout_centerInParent="true"
/>
<TextView
android:id="@+id/tv_disable"
style="@style/common_text_style"
android:layout_width="100dp"
android:layout_alignParentRight="true"
android:height="36dp"
android:background="@drawable/bg_live_red_react"
android:gravity="center"
android:layout_margin="5dp"
android:text="直播中"
android:textColor="@color/white" />
</RelativeLayout>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_title"
style="@style/common_text_style"
android:layout_width="0dp"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:maxLines="2"
android:text="标题" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:src="@drawable/icon_looks" />
<TextView
android:id="@+id/tv_looks"
style="@style/common_text_style"
android:layout_marginLeft="5dp"
android:text="0" />
<ImageView
android:id="@+id/iv_like"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginLeft="10dp"
android:src="@drawable/icon_like_gray" />
<TextView
android:id="@+id/tv_like"
style="@style/common_text_style"
android:layout_marginLeft="5dp"
android:layout_marginRight="10dp"
android:text="0" />
<ImageButton
android:id="@+id/ib_share"
android:layout_width="40dp"
android:layout_height="match_parent"
android:background="@color/transparent"
android:src="@drawable/icon_share_gray" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</layout>
\ No newline at end of file
apply plugin: 'com.android.library'
android {
compileSdkVersion compile_sdk_version
buildToolsVersion build_tools_version
defaultConfig {
minSdkVersion min_sdk_version
targetSdkVersion target_sdk_version
versionCode version_code
versionName verson_name
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildTypes {
release {
minifyEnabled isReleaseMinify
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation fileTree(include: ['*.jar'], dir: 'libs/upload')
implementation "com.android.support:appcompat-v7:27.1.1"
implementation 'com.android.support:recyclerview-v7:27.1.1'
api 'com.tencent.liteav:LiteAVSDK_Professional:latest.release'
// MLVB 需要依赖 okhttp 拦截器
implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1'
// MLVB 需要使用 gson 进行 json 解析
implementation 'com.google.code.gson:gson:2.3.1'
// MLVB 需要依赖腾讯云 IM 服务
api 'com.tencent.imsdk:imsdk:4.5.45'
// 弹幕功能需要依赖此库
implementation 'com.github.ctiao:dfm:0.4.4'
// Demo 依赖的图片加载库
implementation 'com.github.bumptech.glide:glide:4.5.0'
// 美颜面板
implementation project(':beauty')
implementation 'org.greenrobot:eventbus:3.1.1'
// api project(':provider')
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package com.dayu.livemodule;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.dayu.livemodel.test", appContext.getPackageName());
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dayu.livemodule" >
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- IMSDK 权限 -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!--<uses-permission android:name="android.permission.SET_DEBUG_APP" />-->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<application
android:usesCleartextTraffic="true"
android:largeHeap="true"
>
<activity
android:name=".xiaozhibo.login.TCLoginActivity"
android:screenOrientation="portrait"
android:theme="@style/LoginTheme" />
<activity
android:name=".xiaozhibo.login.TCRegisterActivity"
android:screenOrientation="portrait"
android:theme="@style/RegisterTheme" />
<activity
android:name=".xiaozhibo.main.TCMainActivity"
android:screenOrientation="portrait"
android:launchMode="singleTask"/>
<activity
android:name=".xiaozhibo.anchor.prepare.TCAnchorPrepareActivity"
android:screenOrientation="portrait" />
<activity
android:name=".xiaozhibo.audience.TCAudienceActivity"
android:screenOrientation="portrait"
android:theme="@style/PlayerTheme"
android:windowSoftInputMode="adjustNothing" />
<activity
android:name=".xiaozhibo.playback.TCPlaybackActivity"
android:screenOrientation="portrait"
android:theme="@style/PlayerTheme"
android:windowSoftInputMode="adjustNothing" />
<activity
android:name=".xiaozhibo.anchor.TCCameraAnchorActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing" />
<activity
android:name=".xiaozhibo.anchor.screen.TCScreenAnchorActivity"
android:theme="@style/RecordActivityTheme"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing"
android:launchMode="singleTask">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service
android:name=".xiaozhibo.anchor.screen.TCScreenRecordService"
android:enabled="true"
android:exported="false">
</service>
<activity android:name="com.tencent.rtmp.video.TXScreenCapture$TXScreenCaptureAssistantActivity" android:theme="@android:style/Theme.Translucent" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.dayu.bigfish.fileProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
<uses-library
android:name="com.google.android.maps"
android:required="false" />
<uses-library android:name="android.test.runner" />
<service
android:name="com.tencent.imsdk.session.remote.SessionService"
android:process=":network" />
<service
android:name="com.tencent.imsdk.session.remote.AssistService"
android:process=":network" />
<service
android:name="com.tencent.imsdk.session.remote.KeepAliveJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:process=":network" />
<receiver android:name="com.tencent.imsdk.session.SessionBroadcastReceiver" >
<intent-filter>
<action android:name="com.tencent.imsdk.session.boot" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="com.tencent.qcloud.qal.TASK_REMOVED" />
</intent-filter>
</receiver>
</application>
</manifest>
package com.dayu.livemodule;
import android.content.Context;
import com.dayu.livemodule.xiaozhibo.TCGlobalConfig;
import com.dayu.livemodule.xiaozhibo.common.net.TCHTTPMgr;
import com.dayu.livemodule.xiaozhibo.login.TCUserMgr;
import com.tencent.rtmp.TXLiveBase;
import org.json.JSONObject;
public class LiveUtils {
//初始化
public static void initLive(Context context){
TXLiveBase.getInstance().setLicence(context, TCGlobalConfig.LICENCE_URL, TCGlobalConfig.LICENCE_KEY);
// 必须:初始化 MLVB 组件
MLVBLiveRoomImpl.sharedInstance(context);
// 必须:初始化全局的 用户信息管理类,记录个人信息。
TCUserMgr.getInstance().initContext(context);
}
//登录
public static void login(String username, String password) {
final TCUserMgr tcLoginMgr = TCUserMgr.getInstance();
tcLoginMgr.login(username, password, new TCHTTPMgr.Callback() {
@Override
public void onSuccess(JSONObject data) {
}
@Override
public void onFailure(int code, final String msg) {
}
});
}
//初始化用户
public static void initUser(int uid,String username, String avatarUrl) {
final TCUserMgr tcLoginMgr = TCUserMgr.getInstance();
tcLoginMgr.setNickName(username,null);
tcLoginMgr.setAvatar(avatarUrl,null);
tcLoginMgr.login(uid+"", "", new TCHTTPMgr.Callback() {
@Override
public void onSuccess(JSONObject data) {
}
@Override
public void onFailure(int code, final String msg) {
}
});
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
package com.dayu.livemodule;
public class StartPushEvent {
public boolean isMain;
public String pushUrl;
public String roomInfo;
public StartPushEvent(boolean isMain, String pushUrl) {
this.isMain = isMain;
this.pushUrl = pushUrl;
}
}
package com.dayu.livemodule.debug;
import android.util.Base64;
import com.dayu.livemodule.xiaozhibo.TCGlobalConfig;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.zip.Deflater;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* Module: GenerateTestUserSig
*
* Function: 用于生成测试用的 UserSig,UserSig 是腾讯云为其云服务设计的一种安全保护签名。
* 其计算方法是对 SDKAppID、UserID 和 EXPIRETIME 进行加密,加密算法为 HMAC-SHA256。
*
* Attention: 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
*
* 本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品,
* 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
* 一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
*
* 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
* 由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。
*
* Reference:https://cloud.tencent.com/document/product/454/14548#Server
*/
public class GenerateTestUserSig {
/**
* 计算 UserSig 签名
*
* 函数内部使用 HMAC-SHA256 非对称加密算法,对 SDKAPPID、userId 和 EXPIRETIME 进行加密。
*
* @note: 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
*
* 本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品,
* 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
* 一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
*
* 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
* 由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。
*
* 文档:https://cloud.tencent.com/document/product/454/14548#Server
*/
public static String genTestUserSig(String userId) {
return GenTLSSignature(TCGlobalConfig.SDKAPPID, userId, TCGlobalConfig.EXPIRETIME, null, TCGlobalConfig.SECRETKEY);
}
/**
* 生成 tls 票据
*
* @param sdkappid 应用的 appid
* @param userId 用户 id
* @param expire 有效期,单位是秒
* @param userbuf 默认填写null
* @param priKeyContent 生成 tls 票据使用的私钥内容
* @return 如果出错,会返回为空,或者有异常打印,成功返回有效的票据
*/
private static String GenTLSSignature(long sdkappid, String userId, long expire, byte[] userbuf, String priKeyContent) {
long currTime = System.currentTimeMillis() / 1000;
JSONObject sigDoc = new JSONObject();
try {
sigDoc.put("TLS.ver", "2.0");
sigDoc.put("TLS.identifier", userId);
sigDoc.put("TLS.sdkappid", sdkappid);
sigDoc.put("TLS.expire", expire);
sigDoc.put("TLS.time", currTime);
} catch (JSONException e) {
e.printStackTrace();
}
String base64UserBuf = null;
if (null != userbuf) {
base64UserBuf = Base64.encodeToString(userbuf, Base64.NO_WRAP);
try {
sigDoc.put("TLS.userbuf", base64UserBuf);
} catch (JSONException e) {
e.printStackTrace();
}
}
String sig = hmacsha256(sdkappid, userId, currTime, expire, priKeyContent, base64UserBuf);
if (sig.length() == 0) {
return "";
}
try {
sigDoc.put("TLS.sig", sig);
} catch (JSONException e) {
e.printStackTrace();
}
Deflater compressor = new Deflater();
compressor.setInput(sigDoc.toString().getBytes(Charset.forName("UTF-8")));
compressor.finish();
byte[] compressedBytes = new byte[2048];
int compressedBytesLength = compressor.deflate(compressedBytes);
compressor.end();
return new String(base64EncodeUrl(Arrays.copyOfRange(compressedBytes, 0, compressedBytesLength)));
}
private static String hmacsha256(long sdkappid, String userId, long currTime, long expire, String priKeyContent, String base64Userbuf) {
String contentToBeSigned = "TLS.identifier:" + userId + "\n"
+ "TLS.sdkappid:" + sdkappid + "\n"
+ "TLS.time:" + currTime + "\n"
+ "TLS.expire:" + expire + "\n";
if (null != base64Userbuf) {
contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";
}
try {
byte[] byteKey = priKeyContent.getBytes("UTF-8");
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");
hmac.init(keySpec);
byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes("UTF-8"));
return new String(Base64.encode(byteSig, Base64.NO_WRAP));
} catch (UnsupportedEncodingException e) {
return "";
} catch (NoSuchAlgorithmException e) {
return "";
} catch (InvalidKeyException e) {
return "";
} catch (Exception e) {
return "";
}
}
private static byte[] base64EncodeUrl(byte[] input) {
byte[] base64 = new String(Base64.encode(input, Base64.NO_WRAP)).getBytes();
for (int i = 0; i < base64.length; ++i)
switch (base64[i]) {
case '+':
base64[i] = '*';
break;
case '/':
base64[i] = '-';
break;
case '=':
base64[i] = '_';
break;
default:
break;
}
return base64;
}
}
package com.dayu.livemodule.roomutil.commondef;
import android.os.Parcel;
import android.os.Parcelable;
public class AnchorInfo implements Parcelable {
/**
* 用户ID
*/
public String userID;
/**
* 用户昵称
*/
public String userName;
/**
* 用户头像地址
*/
public String userAvatar;
/**
* 低时延拉流地址(带防盗链key)
*/
public String accelerateURL;
public AnchorInfo() {
}
public AnchorInfo(String userID, String userName, String userAvatar, String accelerateURL) {
this.userID = userID;
this.userName = userName;
this.userAvatar = userAvatar;
this.accelerateURL = accelerateURL;
}
protected AnchorInfo(Parcel in) {
this.userID = in.readString();
this.userName = in.readString();
this.accelerateURL = in.readString();
this.userAvatar = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.userID);
dest.writeString(this.userName);
dest.writeString(this.accelerateURL);
dest.writeString(this.userAvatar);
}
public static final Creator<AnchorInfo> CREATOR = new Creator<AnchorInfo>() {
@Override
public AnchorInfo createFromParcel(Parcel source) {
return new AnchorInfo(source);
}
@Override
public AnchorInfo[] newArray(int size) {
return new AnchorInfo[size];
}
};
@Override
public int hashCode() {
return userID.hashCode();
}
@Override
public String toString() {
return "AnchorInfo{" +
"userID='" + userID + '\'' +
", userName='" + userName + '\'' +
", accelerateURL='" + accelerateURL + '\'' +
", userAvatar='" + userAvatar + '\'' +
'}';
}
}
package com.dayu.livemodule.roomutil.commondef;
import org.json.JSONException;
import org.json.JSONObject;
public class AudienceInfo {
public String userID; //观众ID
public String userInfo; //观众信息
public String userName;
public String userAvatar;
public void transferUserInfo() {
JSONObject jsonRoomInfo = null;
try {
jsonRoomInfo = new JSONObject(userInfo);
userName = jsonRoomInfo.optString("userName");
userAvatar = jsonRoomInfo.optString("userAvatar");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
package com.dayu.livemodule.roomutil.commondef;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by jac on 2017/11/14.
* Copyright © 2013-2017 Tencent Cloud. All Rights Reserved.
*/
public class LoginInfo implements Parcelable {
/**
* 直播的appID
*/
public long sdkAppID;
/**
* 自己的用户ID
*/
public String userID;
public String userSig;
/**
* 自己的用户名称
*/
public String userName;
/**
* 自己的头像地址
*/
public String userAvatar;
public LoginInfo() {
}
public LoginInfo(int sdkAppID, String userID, String userName, String userAvatar, String userSig) {
this.sdkAppID = sdkAppID;
this.userID = userID;
this.userName = userName;
this.userAvatar = userAvatar;
this.userSig = userSig;
}
protected LoginInfo(Parcel in) {
this.userID = in.readString();
this.userName = in.readString();
this.userAvatar = in.readString();
this.userSig = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.userID);
dest.writeString(this.userName);
dest.writeString(this.userAvatar);
dest.writeString(this.userSig);
}
public static final Creator<LoginInfo> CREATOR = new Creator<LoginInfo>() {
@Override
public LoginInfo createFromParcel(Parcel source) {
return new LoginInfo(source);
}
@Override
public LoginInfo[] newArray(int size) {
return new LoginInfo[size];
}
};
}
package com.dayu.livemodule.roomutil.commondef;
public class MLVBCommonDef {
public enum CustomFieldOp{
SET/*设置*/, INC/*加计数*/, DEC/*减计数*/
}
public interface LiveRoomErrorCode {
//推流和拉流错误码,请查看 TXLiteAVCode.h
//IM 错误码,请查看 https://cloud.tencent.com/document/product/269/1671
/******************************************
*
* LiveRoom错误码
*
*****************************************/
int OK = 0;
// { 后台错误码
/*msg处理错误*/
int ERROR_CODE_INVALID_MSG = 200100;
int ERROR_CODE_INVALID_JSON = 200101;
/*参数校验错误*/
int ERROR_CODE_INCOMPLETE_PARAM = 201000;
int ERROR_CODE_INCOMPLETE_LOGIN_PARAM = 201001;
int ERROR_CODE_NO_USERID = 201002;
int ERROR_CODE_USERID_NOT_EQUAL = 201003;
int ERROR_CODE_NO_ROOMID = 201004;
int ERROR_CODE_NO_COUNT = 201005;
int ERROR_CODE_NO_MERGE_STREAM_PARAM = 201006;
int ERROR_CODE_OPERATION_EMPTY = 201007;
int ERROR_CODE_UNSUPPORT_OPERATION = 201008;
int ERROR_CODE_SET_FIELD_VALUE_EMPTY = 201009;
/*鉴权错误*/
int ERROR_CODE_VERIFY = 202000;
int ERROR_CODE_VERIFY_FAILED = 202001;
int ERROR_CODE_CONNECTED_TO_IM_SERVER = 202002;
int ERROR_CODE_INVALID_RSP = 202003;
int ERROR_CODE_LOGOUT = 202004;
int ERROR_CODE_APPID_RELATION = 202005;
/*房间操作错误*/
int ERROR_CODE_ROOM_MGR = 203000;
int ERROR_CODE_GET_ROOM_ID = 203001;
int ERROR_CODE_CREATE_ROOM = 203002;
int ERROR_CODE_DESTROY_ROOM = 203003;
int ERROR_CODE_GET_ROOM_LIST = 203004;
int ERROR_CODE_UPDATE_ROOM_MEMBER = 203005;
int ERROR_CODE_ENTER_ROOM = 203006;
int ERROR_CODE_ROOM_PUSHER_TOO_MUCH = 203007;
int ERROR_CODE_INVALID_PUSH_URL = 203008;
int ERROR_CODE_ROOM_NAME_TOO_LONG = 203009;
int ERROR_CODE_USER_NOT_IN_ROOM = 203010;
/*pusher操作错误*/
int ERROR_CODE_PUSHER_MGR = 204000;
int ERROR_CODE_GET_PUSH_URL = 204001;
int ERROR_CODE_GET_PUSHERS = 204002;
int ERROR_CODE_LEAVE_ROOM = 204003;
int ERROR_CODE_GET_PUSH_AND_ACC_URL = 204004;
/*观众操作错误*/
int ERROR_CODE_AUDIENCE_MGR = 205000;
int ERROR_CODE_AUDIENCE_NUM_FULL = 205001;
int ERROR_CODE_ADD_AUDIENCE = 205002;
int ERROR_CODE_DEL_AUDIENCE = 205003;
int ERROR_CODE_GET_AUDIENCES = 205004;
/*心跳处理错误*/
int ERROR_CODE_HEARTBEAT = 206000;
int ERROR_CODE_SET_HEARTBEAT = 206001;
int ERROR_CODE_DEL_HEARTBEAT = 206002;
/*其他错误*/
int ERROR_CODE_OTHER = 207000;
int ERROR_CODE_DB_FAILED = 207001;
int ERROR_CODE_MIX_FAILED = 207002;
int ERROR_CODE_SET_CUSTOM_FIELD = 207003;
int ERROR_CODE_GET_CUSTOM_FIELD = 207004;
int ERROR_CODE_UNSUPPORT_ACTION = 207005;
int ERROR_CODE_UNSUPPORT_ROOM_TYPE = 207006;
// } 后台错误码
// { 客户端错误码
int ERROR_NOT_LOGIN = -1; //未登录
int ERROR_NOT_IN_ROOM = -2; //未进直播房间
int ERROR_PUSH = -3; //推流错误
int ERROR_PARAMETERS_INVALID = -4; //参数错误
int ERROR_LICENSE_INVALID = -5; //license 校验失败
int ERROR_PLAY = -6; //播放错误
int ERROR_IM_FORCE_OFFLINE = -7; // IM 被强制下线(例如:多端登录)
// } 客户端错误码
// @}
}
}
package com.dayu.livemodule.roomutil.commondef;
import android.os.Parcel;
import android.os.Parcelable;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
* Created by jac on 2017/10/30.
*/
public class RoomInfo implements Parcelable {
/**
* 房间ID
*/
public String roomID;
/**
* 房间信息(创建房间时传入)
*/
public String roomInfo;
/**
* 房间名称
*/
public String roomName;
/**
* 房间创建者ID
*/
public String roomCreator;
/**
* 房间创建者的拉流地址(实时模式下不使用该字段;直播模式下就是主播的拉流地址;连麦模式下就是混流地址)
*/
public String mixedPlayURL;
/**
* 房间成员列表
*/
public List<AnchorInfo> pushers;
/**
* 房间观众数
*/
public int audienceCount;
/**
* 房间观众列表
*/
public List<Audience> audiences;
/**
* 房间自定义数据
*/
public String custom;
public static class Audience {
public String userID; //观众ID
public String userInfo; //观众信息
public String userName;
public String userAvatar;
public void transferUserInfo() {
JSONObject jsonRoomInfo = null;
try {
jsonRoomInfo = new JSONObject(userInfo);
userName = jsonRoomInfo.optString("userName");
userAvatar = jsonRoomInfo.optString("userAvatar");
} catch (JSONException e) {
e.printStackTrace();
}
}
}
public RoomInfo() {
}
public RoomInfo(String roomID, String roomInfo, String roomName, String roomCreator, String mixedPlayURL, List<AnchorInfo> anchors) {
this.roomID = roomID;
this.roomInfo = roomInfo;
this.roomName = roomName;
this.roomCreator = roomCreator;
this.mixedPlayURL = mixedPlayURL;
this.pushers = anchors;
}
protected RoomInfo(Parcel in) {
this.roomID = in.readString();
this.roomInfo = in.readString();
this.roomName = in.readString();
this.roomCreator = in.readString();
this.mixedPlayURL = in.readString();
this.pushers = new ArrayList<AnchorInfo>();
in.readList(this.pushers, AnchorInfo.class.getClassLoader());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.roomID);
dest.writeString(this.roomInfo);
dest.writeString(this.roomName);
dest.writeString(this.roomCreator);
dest.writeString(this.mixedPlayURL);
dest.writeList(this.pushers);
}
public static final Creator<RoomInfo> CREATOR = new Creator<RoomInfo>() {
@Override
public RoomInfo createFromParcel(Parcel source) {
return new RoomInfo(source);
}
@Override
public RoomInfo[] newArray(int size) {
return new RoomInfo[size];
}
};
}
package com.dayu.livemodule.roomutil.http;
import com.dayu.livemodule.roomutil.commondef.AnchorInfo;
import com.dayu.livemodule.roomutil.commondef.AudienceInfo;
import com.dayu.livemodule.roomutil.commondef.RoomInfo;
import java.util.List;
import java.util.Map;
/**
* Created by jac on 2017/10/30.
*/
public class HttpResponse {
public int code;
public String message;
public long timestamp;
public transient static int CODE_OK = 0;
public static class LoginResponse extends HttpResponse {
public String userID;
public String token;
}
public static class RoomList extends HttpResponse {
public List<RoomInfo> rooms;
}
public static class PusherList extends HttpResponse {
public String roomID;
public String roomInfo;
public String roomCreator;
public String mixedPlayURL;
public int roomStatusCode;
public List<AnchorInfo> pushers;
}
public static class AudienceList extends HttpResponse {
public List<AudienceInfo> audiences; //观众列表
}
public static class CreateRoom extends HttpResponse {
public String roomID;
}
public static class PushUrl extends HttpResponse {
public String pushURL;
public String accelerateURL;
public String playUrl;
}
public static class PushUrlData extends HttpResponse {
public String subCode;
public String data;
public String msg;
}
public static class MergeStream extends HttpResponse {
public int merge_code;
public String merge_msg;
}
public static class GetCustomInfoResponse extends HttpResponse {
public Map<String, Object> custom;
}
}
package com.dayu.livemodule.roomutil.misc;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by jac on 2017/11/14.
* Copyright © 2013-2017 Tencent Cloud. All Rights Reserved.
*/
public class NameGenerator {
private static final String[] NAMES = {
"宋 江", "卢俊义", "吴 用", "林 冲", "秦 明", "呼延灼", "花 荣", "李 应",
"鲁智深", "武 松", "董 平", "张 清", "扬 志", "徐 宁", "阮小二", "扈三娘",
"韩滔", "萧让", "裴宣", "樊瑞", "圣李衮", "汤隆", "郑天寿", "梦奇", "苏烈",
"大乔", "小乔", "成吉思汗", "诸葛亮", "后羿", "露娜", "吕布","刘邦","雅典娜",
"东皇太一","李元芳", "花木兰", "兰陵王"
};
public static String getRandomName(){
Random random = new Random(System.currentTimeMillis());
int i = Math.abs(random.nextInt()%NAMES.length);
return NAMES[i];
}
public static String getRandomUserID() {
return Long.toHexString(System.currentTimeMillis());
}
public static String replaceNonPrintChar(String s, int limitLength, String concatString, boolean middleConcat){
String tpl="";
if (s != null){
Pattern pattern = Pattern.compile("[\\s]{2,}|\t|\r|\n");
Matcher matcher = pattern.matcher(s);
tpl = matcher.replaceAll(" ");
}
String r = tpl.trim();
int size = ( r.length() - limitLength);
if (size > 0 && limitLength > 0) {
if (concatString != null) {
if (middleConcat) {
int start = r.length() / 2 - size / 2;
int end = r.length() / 2 + size / 2;
StringBuffer sb = new StringBuffer();
String newString = sb.append(r.substring(0, start)).append(concatString).append(r.substring(end, r.length())).toString();
return newString;
} else {
return r.substring(0, limitLength).concat(concatString);
}
}else {
return r.substring(0, limitLength);
}
}
return r;
}
}
package com.dayu.livemodule.xiaozhibo;
/**
* Module: TCGlobalConfig
*
* Function: 小直播 的全局配置类
*
* 1. LiteAVSDK Licence
* 2. 计算腾讯云 UserSig 的 SDKAppId、加密密钥、签名过期时间
* 3. 小直播后台服务器地址
* 4. App 主色调
* 5. 是否启用连麦
*/
public class TCGlobalConfig {
/**
* 1. LiteAVSDK Licence。 用于直播推流鉴权。
*
* 获取License,请参考官网指引 https://cloud.tencent.com/document/product/454/34750
*/
public static final String LICENCE_URL = "http://license.vod2.myqcloud.com/license/v1/39b40793d12654ed50ea2609252b38ea/TXLiveSDK.licence";
public static final String LICENCE_KEY = "b89b0a2d023fd96d04b6888c39eb3e6b";
/**
* 2.1 腾讯云 SDKAppId,需要替换为您自己账号下的 SDKAppId。
*
* 进入腾讯云直播[控制台-直播SDK-应用管理](https://console.cloud.tencent.com/live/license/appmanage) 创建应用,即可看到 SDKAppId,
* 它是腾讯云用于区分客户的唯一标识。
*/
public static final int SDKAPPID = 1400377925;
/**
* 2.2 计算签名用的加密密钥,获取步骤如下:
*
* step1. 进入腾讯云直播[控制台-直播SDK-应用管理](https://console.cloud.tencent.com/live/license/appmanage),如果还没有应用就创建一个,
* step2. 单击您的应用,进入"应用管理"页面。
* step3. 点击“查看密钥”按钮,就可以看到计算 UserSig 使用的加密的密钥了,请将其拷贝并复制到如下的变量中。
* 如果提示"请先添加管理员才能生成公私钥",点击"编辑",输入管理员名称,如"admin",点"确定"添加管理员。然后再查看密钥。
*
* 注意:该方案仅适用于调试Demo,正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上,以避免加密密钥泄露导致的流量盗用。
* 文档:https://cloud.tencent.com/document/product/647/17275#Server
*/
public static final String SECRETKEY = "3e557abda6657c4d4af5911e0779a9a607588711e039ee516abc28bdbe4918d0";
/**
* 2.3 签名过期时间,建议不要设置的过短
* <p>
* 时间单位:秒
* 默认时间:7 x 24 x 60 x 60 = 604800 = 7 天
*/
public static final int EXPIRETIME = 604800;
/**
* 3. 小直播后台服务器地址
*
* 3.1 您可以不填写后台服务器地址:
* 小直播 App 单靠客户端源码运行,方便快速跑通体验小直播。
* 不过在这种模式下运行的“小直播”,没有注册登录、回放列表等功能,仅有基本的直播推拉流、聊天室、连麦等功能。
* 另外在这种模式下,腾讯云安全签名 UserSig 是使用本地 GenerateTestUserSig 模块计算的,存在 SECRETKEY 被破解的导致腾讯云流量被盗用的风险。
*
* 3.2 您可以填写后台服务器地址:
* 服务器需要您参考文档 https://cloud.tencent.com/document/product/454/15187 自行搭建。
* 服务器提供注册登录、回放列表、计算 UserSig 等服务。
* 这种情况下 {@link #SDKAPPID} 和 {@link #SECRETKEY} 可以设置为任意值。
*
* 注意:
* 后台服务器地址(APP_SVR_URL)和 (SDKAPPID,SECRETKEY)一定要填一项。
* 要么填写后台服务器地址(@link #APP_SVR_URL),要么填写 {@link #SDKAPPID} 和 {@link #SECRETKEY}。
*
* 详情请参考:
*/
public static final String APP_SVR_URL = "";
/**
* 4. App 主色调。
*/
public static final int MAIN_COLOR = 0xff222B48;
/**
* 5. 是否启用连麦。
*
* 由于连麦功能使用了比较昂贵的 BGP 专用线路,所以是按照通话时长进行收费的。最初级的体验包包含 3000 分钟的连麦时长,只需要 9.8 元。
* 购买链接:https://buy.cloud.tencent.com/mobilelive?urlctr=yes&micconn=3000m##
*/
public static final boolean ENABLE_LINKMIC = true;
}
package com.dayu.livemodule.xiaozhibo.anchor;
import android.app.Dialog;
import android.app.DialogFragment;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.dayu.livemodule.R;
/**
* Module: FinishDetailDialogFragment
* <p>
* Function: 推流结束的详情页
*
* 统计了观看人数、点赞数量、开播时间
*/
public class FinishDetailDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Dialog mDetailDialog = new Dialog(getActivity(), R.style.dialog);
mDetailDialog.setContentView(R.layout.dialog_publish_detail);
mDetailDialog.setCancelable(false);
TextView tvCancel = (TextView) mDetailDialog.findViewById(R.id.anchor_btn_cancel);
tvCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDetailDialog.dismiss();
getActivity().finish();
}
});
TextView tvDetailTime = (TextView) mDetailDialog.findViewById(R.id.tv_time);
TextView tvDetailAdmires = (TextView) mDetailDialog.findViewById(R.id.tv_admires);
TextView tvDetailWatchCount = (TextView) mDetailDialog.findViewById(R.id.tv_members);
//确认则显示观看detail
tvDetailTime.setText(getArguments().getString("time"));
tvDetailAdmires.setText(getArguments().getString("heartCount"));
tvDetailWatchCount.setText(getArguments().getString("totalMemberCount"));
return mDetailDialog;
}
}
package com.dayu.livemodule.xiaozhibo.anchor.music;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
public class MusicEntity implements Parcelable {
//id标识
public int id;
// 显示名称
public String title;
// 文件名称
public String display_name;
// 音乐文件的路径
public String path;
// 媒体播放总时间
public int duration;
// 专辑
public String albums;
// 艺术家
public String artist;
//歌手
public String singer;
public String durationStr;
public long size;
//0:idle 1:playing
public char state = 0;
public Uri fileUri;
MusicEntity() {
}
protected MusicEntity(Parcel in) {
id = in.readInt();
title = in.readString();
display_name = in.readString();
path = in.readString();
duration = in.readInt();
albums = in.readString();
artist = in.readString();
singer = in.readString();
durationStr = in.readString();
size = in.readLong();
state = (char) in.readInt();
fileUri = in.readParcelable(Uri.class.getClassLoader());
}
public static final Creator<MusicEntity> CREATOR = new Creator<MusicEntity>() {
@Override
public MusicEntity createFromParcel(Parcel in) {
return new MusicEntity(in);
}
@Override
public MusicEntity[] newArray(int size) {
return new MusicEntity[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(title);
dest.writeString(display_name);
dest.writeString(path);
dest.writeInt(duration);
dest.writeString(albums);
dest.writeString(artist);
dest.writeString(singer);
dest.writeString(durationStr);
dest.writeLong(size);
dest.writeInt(state);
dest.writeParcelable(fileUri, flags);
}
@NonNull
@Override
public String toString() {
return "MusicEntity{" +
"id=" + id +
", title='" + title + '\'' +
", display_name='" + display_name + '\'' +
", path='" + path + '\'' +
", duration='" + duration + '\'' +
", albums=" + albums +
", artist=" + artist +
", singer=" + singer +
", durationStr=" + durationStr +
", size=" + size +
", state=" + state +
", fileUri=" + fileUri +
'}';
}
}
\ No newline at end of file
package com.dayu.livemodule.xiaozhibo.anchor.music;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.dayu.livemodule.R;
import java.util.List;
/**
* Module: MusicListAdapter
* <p>
* Function: 音乐列表的 Adapter
*/
class MusicListAdapter extends BaseAdapter {
private List<MusicEntity> mData;
private LayoutInflater mInflater;
MusicListAdapter(LayoutInflater inflater, List<MusicEntity> list) {
mInflater = inflater;
mData = list;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item_music, null);
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.music_tv_name);
holder.duration = (TextView) convertView.findViewById(R.id.music_tv_duration);
holder.selected = (ImageView) convertView.findViewById(R.id.music_iv_selected);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.name.setText(mData.get(position).title);
holder.duration.setText(mData.get(position).durationStr);
holder.selected.setVisibility(mData.get(position).state == 1 ? View.VISIBLE : View.GONE);
return convertView;
}
private static class ViewHolder {
ImageView selected;
TextView name;
TextView duration;
}
}
\ No newline at end of file
package com.dayu.livemodule.xiaozhibo.anchor.music;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.BaseAdapter;
import android.widget.ListView;
import java.util.List;
/**
* Module: MusicListView
* <p>
* Function: 音乐列表的 ListView
*/
public class MusicListView extends ListView {
private List<MusicEntity> mData;
private BaseAdapter mAdapter;
public BaseAdapter getAdapter() {
return mAdapter;
}
public MusicListView(Context context) {
super(context);
this.setChoiceMode(CHOICE_MODE_SINGLE);
}
public MusicListView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setChoiceMode(CHOICE_MODE_SINGLE);
}
public void setupList(LayoutInflater inflater, List<MusicEntity> data) {
mData = data;
mAdapter = new MusicListAdapter(inflater, data);
setAdapter(mAdapter);
}
public void setData(List<MusicEntity> data) {
mData = data;
}
@Override
public int getCount() {
return mData.size();
}
}
package com.dayu.livemodule.xiaozhibo.anchor.music;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import com.dayu.livemodule.R;
import com.dayu.livemodule.xiaozhibo.common.widget.TCActivityTitle;
import java.util.List;
/**
* Module: TCMusicSelectView
* <p>
* Function: 音乐列表的选择界面
*/
public class TCMusicSelectView extends LinearLayout{
private TCAudioControl mAudioCtrl;
private TCActivityTitle TvTitle;
private Context mContext;
public MusicListView mMusicList;
public Button mBtnAutoSearch;
public TCMusicSelectView (Context context, AttributeSet attrs){
super(context,attrs);
mContext = context;
}
public TCMusicSelectView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
}
public TCMusicSelectView(Context context) {
super(context);
mContext = context;
}
public void init(TCAudioControl audioControl, List<MusicEntity> data){
mAudioCtrl = audioControl;
LayoutInflater.from(mContext).inflate(R.layout.layout_music_chose,this);
mMusicList = (MusicListView)findViewById(R.id.music_list_view);
mMusicList.setData(data);
mBtnAutoSearch = (Button)findViewById(R.id.music_btn_search);
TvTitle = (TCActivityTitle)findViewById(R.id.music_ac_title);
TvTitle.setReturnListener(new OnClickListener() {
@Override
public void onClick(View v) {
mAudioCtrl.mMusicSelectView.setVisibility(GONE);
mAudioCtrl.mMusicControlPart.setVisibility(VISIBLE);
}
});
}
}
package com.dayu.livemodule.xiaozhibo.anchor.prepare;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.text.TextUtils;
import android.util.Log;
import com.dayu.livemodule.R;
import com.dayu.livemodule.xiaozhibo.common.utils.TCConstants;
import java.io.IOException;
import java.util.List;
/**
* Module: TCLocationHelper
* <p>
* Function: 定位服务的工具类
*
* 该工具能提供粗略的定位服务,若您需要高精度定位,可以使用腾讯云 LBS SDK进行位置定位。
*
* 详情见:https://lbs.qq.com/geo/index.html
*/
public class TCLocationHelper {
private static String TAG = "LocationHelper";
private static LocationListener mLocationListener;
static public boolean checkLocationPermission(final @NonNull Activity activity) {
if (Build.VERSION.SDK_INT >= 23) {
if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION)) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, TCConstants.LOCATION_PERMISSION_REQ_CODE);
return false;
}
}
return true;
}
static private String getAddressFromLocation(final @NonNull Activity activity, Location location) {
Geocoder geocoder = new Geocoder(activity);
try {
double latitude = location.getLatitude();
double longitude = location.getLongitude();
Log.d(TAG, "getAddressFromLocation->lat:" + latitude + ", long:" + longitude);
List<Address> list = geocoder.getFromLocation(latitude, longitude, 1);
if (list.size() > 0) {
//返回当前位置,精度可调
Address address = list.get(0);
String sAddress;
if(!TextUtils.isEmpty(address.getLocality())) {
if(!TextUtils.isEmpty(address.getFeatureName())) {
sAddress = address.getLocality() + " " + address.getFeatureName();
} else {
sAddress = address.getLocality();
}
} else
sAddress = "定位失败";
return sAddress;
}
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
static public boolean getMyLocation(final @NonNull Activity activity, final @NonNull OnLocationListener listener) {
final LocationManager locationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
if(!locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)){
// notify user
AlertDialog.Builder dialog = new AlertDialog.Builder(activity, R.style.ConfirmDialogStyle);
dialog.setMessage("尚未开启位置定位服务");
dialog.setPositiveButton("开启", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface paramDialogInterface, int paramInt) {
// TODO Auto-generated method stub
Intent myIntent = new Intent( Settings.ACTION_LOCATION_SOURCE_SETTINGS);
activity.startActivity(myIntent);
//get gps
}
});
dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface paramDialogInterface, int paramInt) {
// TODO Auto-generated method stub
}
});
dialog.show();
return false;
}
if (!checkLocationPermission(activity)) {
return true;
}
Location curLoc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
if (null == curLoc) {
mLocationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
String strAddr = getAddressFromLocation(activity, location);
if (TextUtils.isEmpty(strAddr)) {
listener.onLocationChanged(-1, 0, 0, strAddr);
} else {
listener.onLocationChanged(0, location.getLatitude(), location.getLongitude(), strAddr);
}
locationManager.removeUpdates(this);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
locationManager.removeUpdates(this);
}
@Override
public void onProviderEnabled(String provider) {
locationManager.removeUpdates(this);
}
@Override
public void onProviderDisabled(String provider) {
locationManager.removeUpdates(this);
}
};
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 8000, 0, mLocationListener);
} else {
String strAddr = getAddressFromLocation(activity, curLoc);
if (TextUtils.isEmpty(strAddr)) {
listener.onLocationChanged(-1, 0, 0, strAddr);
} else {
listener.onLocationChanged(0, curLoc.getLatitude(), curLoc.getLongitude(), strAddr);
}
}
return true;
}
public interface OnLocationListener {
void onLocationChanged(int code, double lat1, double long1, String location);
}
}
package com.dayu.livemodule.xiaozhibo.anchor.screen;
import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import com.dayu.livemodule.R;
import com.dayu.livemodule.xiaozhibo.anchor.TCBaseAnchorActivity;
import com.dayu.livemodule.xiaozhibo.anchor.screen.widget.FloatingCameraView;
import com.dayu.livemodule.xiaozhibo.anchor.screen.widget.FloatingView;
import com.dayu.livemodule.xiaozhibo.common.report.TCELKReportMgr;
import com.dayu.livemodule.xiaozhibo.common.utils.TCConstants;
import com.dayu.livemodule.xiaozhibo.login.TCUserMgr;
/**
* Module: TCScreenAnchorActivity
* <p>
* Function: 屏幕录制推流的页面
* <p>
*
* 注:Android 在 API 21+ 的版本才支持屏幕录制功能
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class TCScreenAnchorActivity extends TCBaseAnchorActivity {
private static final String TAG = TCScreenAnchorActivity.class.getSimpleName();
public static int OVERLAY_PERMISSION_REQ_CODE = 1234;
//悬浮摄像窗以及悬浮球
private FloatingView mFloatingView; // 悬浮球
private FloatingCameraView mFloatingCameraView; // 悬浮摄像框
private ImageView mCameraBtn; // 开启-关闭摄像头按钮
private boolean mInCamera = false; // 摄像头是否打开
private Intent serviceIntent; // 后台服务
private long mStartPushPts; // APP统计录屏推流,您可以忽略
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//启动后台拉活进程(处理强制下线消息)
serviceIntent = new Intent();
serviceIntent.setClassName(this, TCScreenRecordService.class.getName());
startService(serviceIntent);
TCELKReportMgr.getInstance().reportELK(TCConstants.ELK_ACTION_SCREEN_PUSH, TCUserMgr.getInstance().getUserId(), 0, "录屏推流", null);
mStartPushPts = System.currentTimeMillis();
}
protected void initView() {
setContentView(R.layout.activity_screen_anchor);
super.initView();
//悬浮球界面
mFloatingView = new FloatingView(getApplicationContext(), R.layout.view_floating_default);
mFloatingView.setPopupWindow(R.layout.popup_layout);
mFloatingCameraView = new FloatingCameraView(getApplicationContext());
mCameraBtn = (ImageView) mFloatingView.getPopupView().findViewById(R.id.btn_camera);
mFloatingView.setOnPopupItemClickListener(this);
}
@Override
protected void onResume() {
super.onResume();
if (mFloatingView.isShown()) {
mFloatingView.dismiss();
}
if (null != mFloatingCameraView && mFloatingCameraView.isShown()) {
mFloatingCameraView.dismiss();
mCameraBtn.setImageResource(R.mipmap.camera_off);
mInCamera = false;
}
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
requestDrawOverLays();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mFloatingView.isShown()) {
mFloatingView.dismiss();
}
if (null != mFloatingCameraView) {
if (mFloatingCameraView.isShown()) {
mFloatingCameraView.dismiss();
}
mFloatingCameraView.release();
}
//unbindService(mServiceConn);
stopService(serviceIntent);
stopPublish();
long endPushPts = System.currentTimeMillis();
long diff = (endPushPts - mStartPushPts) / 1000;
TCELKReportMgr.getInstance().reportELK(TCConstants.ELK_ACTION_SCREEN_PUSH_DURATION, TCUserMgr.getInstance().getUserId(), diff, "录屏推流时长", null);
}
/**
* /////////////////////////////////////////////////////////////////////////////////
* //
* // 推流相关
* //
* /////////////////////////////////////////////////////////////////////////////////
*/
protected void startPublish() {
mLiveRoom.setListener(this);
mLiveRoom.setCameraMuteImage(BitmapFactory.decodeResource(getResources(), R.mipmap.recording_background_private_vertical));
mLiveRoom.startScreenCapture();
super.startPublish();
}
/**
* /////////////////////////////////////////////////////////////////////////////////
* //
* // 浮窗相关
* //
* /////////////////////////////////////////////////////////////////////////////////
*/
public void requestDrawOverLays() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N && !Settings.canDrawOverlays(TCScreenAnchorActivity.this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + TCScreenAnchorActivity.this.getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
} else {
showFloatingView();
}
}
private void showFloatingView() {
if (!mFloatingView.isShown()) {
if ((null != mLiveRoom)) {
mFloatingView.show();
mFloatingView.setOnPopupItemClickListener(this);
}
}
}
/**
* /////////////////////////////////////////////////////////////////////////////////
* //
* // 点击事件与调用函数相关
* //
* /////////////////////////////////////////////////////////////////////////////////
*/
@Override
public void onClick(View v) {
int i = v.getId();
if (i == R.id.btn_return) {//悬浮球返回主界面按钮
Toast.makeText(getApplicationContext(), "返回主界面", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(getApplicationContext(), TCScreenAnchorActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// getApplicationContext().startActivity(intent);
try {
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
pendingIntent.send();
} catch (Exception e) {
e.printStackTrace();
}
} else if (i == R.id.btn_camera) {//camera悬浮窗
triggerFloatingCameraView();
} else if (i == R.id.btn_close) {
showExitInfoDialog("当前正在直播,是否退出直播?", false);
} else {
super.onClick(v);
}
}
/**
* 处理cameraview初始化、权限申请 以及 cameraview的显示与隐藏
*/
public void triggerFloatingCameraView() {
//trigger
if (mInCamera) {
Toast.makeText(getApplicationContext(), "关闭摄像头", Toast.LENGTH_SHORT).show();
mCameraBtn.setImageResource(R.mipmap.camera_off);
mFloatingCameraView.dismiss();
} else {
//show失败显示错误信息
if (!mFloatingCameraView.show()) {
Toast.makeText(getApplicationContext(), "打开摄像头权限失败,请在系统设置打开摄像头权限", Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(getApplicationContext(), "打开摄像头", Toast.LENGTH_SHORT).show();
mCameraBtn.setImageResource(R.mipmap.camera_on);
}
mInCamera = !mInCamera;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N && !Settings.canDrawOverlays(TCScreenAnchorActivity.this)) {
Toast.makeText(getApplicationContext(), "请在设置-权限设置里打开悬浮窗权限", Toast.LENGTH_SHORT).show();
} else {
showFloatingView();
}
}
}
}
package com.dayu.livemodule.xiaozhibo.anchor.screen;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import com.dayu.livemodule.xiaozhibo.common.utils.TCConstants;
import com.tencent.rtmp.TXLog;
/**
* 前台进程
* 添加广播消息监听下线通知,若被挤下线则强制拉起陆平页面显示错误信息
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class TCScreenRecordService extends Service {
private static final String TAG = TCScreenRecordService.class.getSimpleName();
//被踢下线广播监听
private LocalBroadcastManager mLocalBroadcatManager;
private BroadcastReceiver mExitBroadcastReceiver;
public TCScreenRecordService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onCreate() {
super.onCreate();
Notification.Builder builder = new Notification.Builder(this);
Intent intent = new Intent(this, TCScreenAnchorActivity.class);
final PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
builder.setContentIntent(contentIntent);
builder.setTicker("Foreground Service Start");
builder.setContentTitle("正在进行录制");
Notification notification = builder.build();
//把该service创建为前台service
startForeground(1, notification);
mLocalBroadcatManager = LocalBroadcastManager.getInstance(this);
mExitBroadcastReceiver = new ExitBroadcastRecevier();
mLocalBroadcatManager.registerReceiver(mExitBroadcastReceiver, new IntentFilter(TCConstants.EXIT_APP));
}
@Override
public void onDestroy() {
super.onDestroy();
mLocalBroadcatManager.unregisterReceiver(mExitBroadcastReceiver);
}
public class ExitBroadcastRecevier extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(TCConstants.EXIT_APP)) {
TXLog.d(TAG, "service broadcastReceiver receive exit app msg");
//唤醒activity提示推流结束
Intent restartIntent = new Intent(getApplicationContext(), TCScreenAnchorActivity.class);
restartIntent.setAction(TCConstants.EXIT_APP);
restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(restartIntent);
}
}
}
}
package com.dayu.livemodule.xiaozhibo.anchor.screen.widget;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
/**
* 跟随手指移动View以及显示/隐藏的接口封装
*/
public abstract class BaseFloatingView extends FrameLayout implements GestureDetector.OnGestureListener {
protected final Context mContext;
protected WindowManager mWindowManager;
private GestureDetector mGestureDetector;
private WindowManager.LayoutParams layoutParams;
private float lastX, lastY;
public BaseFloatingView(Context context) {
super(context);
this.mContext = context;
this.mGestureDetector = new GestureDetector(context, this);
}
public BaseFloatingView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
this.mGestureDetector = new GestureDetector(context, this);
}
public BaseFloatingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
this.mGestureDetector = new GestureDetector(context, this);
}
protected void showView(View view) {
showView(view, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
}
protected void showView(View view, int width, int height) {
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
//TYPE_TOAST仅适用于4.4+系统,假如要支持更低版本使用TYPE_SYSTEM_ALERT(需要在manifest中声明权限)
//7.1(包含)及以上系统对TYPE_TOAST做了限制
int type = WindowManager.LayoutParams.TYPE_TOAST;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
type = WindowManager.LayoutParams.TYPE_PHONE;
}
layoutParams = new WindowManager.LayoutParams(type);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
//layoutParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; //no limit适用于超出屏幕的情况,若添加此flag需要增加边界检测逻辑
layoutParams.width = width;
layoutParams.height = height;
layoutParams.format = PixelFormat.TRANSLUCENT;
mWindowManager.addView(view, layoutParams);
}
protected void hideView() {
if (null != mWindowManager)
mWindowManager.removeViewImmediate(this);
mWindowManager = null;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent e) {
lastX = e.getRawX();
lastY = e.getRawY();
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
float nowX, nowY, tranX, tranY;
// 获取移动时的X,Y坐标
nowX = e2.getRawX();
nowY = e2.getRawY();
// 计算XY坐标偏移量
tranX = nowX - lastX;
tranY = nowY - lastY;
// 移动悬浮窗
layoutParams.x += tranX;
layoutParams.y += tranY;
//更新悬浮窗位置
mWindowManager.updateViewLayout(this, layoutParams);
//记录当前坐标作为下一次计算的上一次移动的位置坐标
lastX = nowX;
lastY = nowY;
return false;
}
@Override
public void onLongPress(MotionEvent e) {
//Toast.makeText(mContext, "onLongPress", Toast.LENGTH_SHORT).show();
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
}
package com.dayu.livemodule.xiaozhibo.anchor.screen.widget;
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import java.io.IOException;
import java.util.List;
/**
* Created by Administrator on 2016/9/13
* 摄像头 srufaceview
*/
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = CameraPreview.class.getSimpleName();
private final SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context) {
super(context);
this.mHolder = getHolder();
this.mHolder.addCallback(this);
}
public void setCamera(Camera camera) {
mCamera = camera;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.e(TAG, "Error setting camera preview: " + e.getMessage());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mHolder.getSurface() == null) {
return;
}
// 在修改设置前停止preview
try {
mCamera.stopPreview();
} catch (Exception e) {
// Tried to stop a non-existent preview, so ignore.
}
Log.d(TAG, "surfaceChanged");
// camera设置
try {
this.setFocusable(true);
this.setFocusableInTouchMode(true);
mCamera.setDisplayOrientation(getDisplayOrientation());
Camera.Parameters params = mCamera.getParameters();
Camera.Size optimalSize = getOptimalPreviewSize(height);
params.setPreviewSize(optimalSize.width, optimalSize.height);
mCamera.setParameters(params);
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "surfaceDestroyed(SurfaceHolder");
}
/**
* 获取当前适合的渲染方向
* @return degree角度 0 - 360
*/
public int getDisplayOrientation() {
Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int rotation = display.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_FRONT, info);
int result;
result = (info.orientation - degrees + 180) % 360;
Log.d("rotationcalc", "info.ori:" + info.orientation + "degrees:" + degrees + " result:" + result);
return result;
}
/**
* 设定摄像头方向
*/
public void setCameraOrientation() {
try {
mCamera.stopPreview();
} catch (Exception e) {
// Tried to stop a non-existent preview, so ignore.
}
try {
this.setFocusable(true);
this.setFocusableInTouchMode(true);
//设置
mCamera.setDisplayOrientation(getDisplayOrientation());
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
/**
* 获取当前环境下最佳的camera size
* @param target 目标长宽
* @return Camera.Size 当前摄像头支持最佳大小
*/
public Camera.Size getOptimalPreviewSize(int target) {
if (mCamera == null)
return null;
List<Camera.Size> sizes = mCamera.getParameters().getSupportedPreviewSizes();
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - target) + Math.abs(size.width - target) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - target) + Math.abs(size.width - target);
}
}
return optimalSize;
}
}
package com.dayu.livemodule.xiaozhibo.anchor.screen.widget;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.Camera;
import android.util.Log;
import android.view.ViewGroup;
import android.view.WindowManager;
import com.dayu.livemodule.xiaozhibo.common.utils.TCUtils;
import com.tencent.rtmp.TXLog;
/**
* Created by Administrator on 2016/9/13
* 摄像头悬浮窗
*/
public class FloatingCameraView extends BaseFloatingView {
private static final String TAG = FloatingCameraView.class.getSimpleName();
private Camera mCamera; // camera 在每次显示时创建,关闭时销毁
private CameraPreview mPreview; // camera preview
private int mOrientation; // 当前屏幕方向缓存
ScreenBroadcastReceiver mReceiver;
public FloatingCameraView(Context context) {
super(context);
mReceiver = new ScreenBroadcastReceiver();
if (context != null) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
context.registerReceiver(mReceiver, filter);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// 检测旋屏事件,若存在旋屏则更新 surface 角度
if (mWindowManager != null)
if (mOrientation != mWindowManager.getDefaultDisplay().getRotation()) {
mOrientation = mWindowManager.getDefaultDisplay().getRotation();
Log.d("boom!", "onlayout orientationchanged");
ViewGroup.LayoutParams lp = mPreview.getLayoutParams();
Camera.Size size = mPreview.getOptimalPreviewSize(TCUtils.dp2pxConvertInt(mContext, 120));
//在某些角度摄像头翻转方向后长宽比相反
if(mOrientation == 0 || mOrientation == 2) {
lp.width = size.height;
lp.height = size.width;
} else {
lp.width = size.width;
lp.height = size.height;
}
updateViewLayout(mPreview, lp);
mPreview.setCameraOrientation();
}
}
private Camera getFacingFrontCamera() {
try {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
Camera.getCameraInfo(i, cameraInfo);// 得到每一个摄像头的信息
// 取前置摄像头
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
return Camera.open(i);
}
}
} catch (RuntimeException e) {
e.printStackTrace();
TXLog.e(TAG, e.getMessage());
}
return null;
}
public void release() {
if (null != mCamera)
mCamera.release();
try {
mContext.unregisterReceiver(mReceiver);
} catch (Exception e) {
}
}
/**
* 显示摄像头悬浮窗
*/
public boolean show() {
// 获取前置摄像头
mCamera = getFacingFrontCamera();
if (null == mCamera)
return false;
if (null == mPreview) {
//第一次创建
mPreview = new CameraPreview(mContext);
mPreview.setCamera(mCamera);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
// 获取最接近 120dp*120dp 的摄像头 preview
Camera.Size size = mPreview.getOptimalPreviewSize(TCUtils.dp2pxConvertInt(mContext, 120));
if (null == size)
return false;
//摄像头翻转方向后长宽比相反
lp.width = size.height;
lp.height = size.width;
addView(mPreview, lp);
} else {
// preview 已执行 addview,注入 camera 对象即可
mPreview.setCamera(mCamera);
}
super.showView(this);
return true;
}
/**
* 隐藏摄像头悬浮窗
*/
public void dismiss() {
super.hideView();
// 回收 camera 对象
if (null != mCamera)
mCamera.release();
mCamera = null;
}
private class ScreenBroadcastReceiver extends BroadcastReceiver {
private boolean mHidden = false;
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
String action = intent.getAction();
if (Intent.ACTION_SCREEN_ON.equals(action)) {
if (mHidden) {
show();
mHidden = false;
}
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
if (isShown()) {
dismiss();
mHidden = true;
}
}
}
}
}
}
package com.dayu.livemodule.xiaozhibo.anchor.screen.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupWindow;
/**
* Created by Administrator on 2016/9/12
* 悬浮球,点击弹出菜单
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class FloatingView extends BaseFloatingView {
private PopupWindow mPopupWindow;
private long mTapOutsideTime;
private boolean isShowing = false;
/**
* 悬浮球
* @param context 建议使用application context避免activity泄漏
* @param viewResId Resid
*/
public FloatingView(Context context, int viewResId) {
super(context);
View.inflate(context, viewResId, this);
}
/**
* 设置弹出菜单
* @param id resource id,根据resource id inflate 菜单
*/
public void setPopupWindow(int id) {
mPopupWindow = new PopupWindow(this);
mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow.setTouchable(true);
mPopupWindow.setOutsideTouchable(true);
mPopupWindow.setFocusable(false);
mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
mPopupWindow.setContentView(LayoutInflater.from(getContext()).inflate(id, null));
mPopupWindow.setTouchInterceptor(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
mPopupWindow.dismiss();
mTapOutsideTime = System.currentTimeMillis();
return true;
}
return false;
}
});
}
/**
*
* 获取 popupWindow 顶层 view
*/
public View getPopupView() {
return mPopupWindow.getContentView();
}
/**
* 注册点击回调
*/
public void setOnPopupItemClickListener(OnClickListener listener) {
if (mPopupWindow == null)
return;
ViewGroup layout = (ViewGroup)mPopupWindow.getContentView();
for (int i = 0; i < layout.getChildCount(); i++) {
layout.getChildAt(i).setOnClickListener(listener);
}
}
/**
* 显示悬浮球
*/
public void show() {
if (!isShowing) super.showView(this);
isShowing = true;
}
/**
* 关闭悬浮球
*/
public void dismiss() {
if (isShowing)
super.hideView();
isShowing = false;
// 清空 listener
ViewGroup layout = (ViewGroup)mPopupWindow.getContentView();
for (int i = 0; i < layout.getChildCount(); i++) {
layout.getChildAt(i).setOnClickListener(null);
}
}
/**
* 单击显示popupWindow
* @param e motionEvent
* @return false 本身不对点击事件进行消费
*/
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (null != mPopupWindow)
mPopupWindow.dismiss();
// 避免单击悬浮球不断出现 popupwindows
if (!(System.currentTimeMillis() - mTapOutsideTime < 80)) {
mPopupWindow.showAtLocation(this, Gravity.NO_GRAVITY, 100, 0);
}
return false;
}
}
package com.dayu.livemodule.xiaozhibo.audience;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.widget.ImageView;
import com.dayu.livemodule.R;
/**
* Module: TCCustomSwitch
*
* Function: 带有动画的 Switch 类
*
*/
public class TCCustomSwitch extends ImageView {
private boolean mChecked = false;
private AnimationDrawable mAniDraw;
private Handler mAnimHandler;
private Runnable mRunnable;
private void init(){
mAnimHandler = new Handler();
mRunnable = new Runnable() {
@Override
public void run() {
onAnimationFinish();
}
};
}
public TCCustomSwitch(Context context) {
super(context);
init();
}
public TCCustomSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TCCustomSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public int getTotalDuration(AnimationDrawable anidraw){
int iDuration = 0;
for (int i=0; i<anidraw.getNumberOfFrames(); i++){
iDuration += anidraw.getDuration(i);
}
return iDuration;
}
/**
* 动画播放结束
*/
private void onAnimationFinish(){
if (mChecked) {
setImageResource(R.drawable.btn_switch_on);
}else{
setImageResource(R.drawable.btn_switch_off);
}
}
/**
* 更新switch状态并播放动画
* @param bCheck
* @param bPlayAnim
*/
public void setChecked(boolean bCheck, boolean bPlayAnim){
if (bCheck == mChecked){
return;
}
mChecked = bCheck;
if (bPlayAnim) {
setImageResource(mChecked ? R.drawable.switch_open : R.drawable.switch_close);
mAniDraw = (AnimationDrawable) getDrawable();
mAniDraw.start();
mAnimHandler.postDelayed(mRunnable, getTotalDuration(mAniDraw));
}else{
onAnimationFinish();
}
}
public boolean getChecked(){
return mChecked;
}
}
package com.dayu.livemodule.xiaozhibo.audience;
/**
* Module: TCFrequeControl
*
* Function: 频率控制类
*
* 用于控制一定时间内的触发次数不能过多
* 规则为在设定时间内,最开始的nCounts次请求能触发;忽略后面超出的请求,距离上个时间段内首次触发时间超过设定时间后重启下一个时间段逻辑。
*/
public class TCFrequeControl {
private int mCounts = 0; //设定时间内允许触发的次数
private int mSeconds = 0; //设定的时间秒数
private int mCurrentCounts = 0; //当前已经触发的次数
private long mFirstTriggerTime = 0; //当前时间段内首次触发的时间
public void init(int nCounts, int nSeconds) {
this.mCounts = nCounts;
this.mSeconds = nSeconds;
this.mCurrentCounts = 0;
this.mFirstTriggerTime = 0;
}
public boolean canTrigger() {
long time = System.currentTimeMillis();
//重置首次触发时间和已经触发次数
if (mFirstTriggerTime == 0 || time - mFirstTriggerTime > 1000 * mSeconds) {
mFirstTriggerTime = time;
mCurrentCounts = 0;
}
//已经触发了mCounts次,本次不能触发
if (mCurrentCounts >= mCounts) {
return false;
}
++mCurrentCounts;
return true;
}
}
package com.dayu.livemodule.xiaozhibo.common.msg;
/**
* Module: TCChatEntity
* <p>
* Function: 消息载体类。
*/
public class TCChatEntity {
private String grpSendName; // 发送者的名字
private String content; // 消息内容
private int type; // 消息类型
public String getSenderName() {
return grpSendName != null ? grpSendName : "";
}
public void setSenderName(String grpSendName) {
this.grpSendName = grpSendName;
}
public String getContent() {
return content;
}
public void setContent(String context) {
this.content = context;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TCChatEntity)) return false;
TCChatEntity that = (TCChatEntity) o;
if (getType() != that.getType()) return false;
if (grpSendName != null ? !grpSendName.equals(that.grpSendName) : that.grpSendName != null)
return false;
return getContent() != null ? getContent().equals(that.getContent()) : that.getContent() == null;
}
@Override
public int hashCode() {
int result = grpSendName != null ? grpSendName.hashCode() : 0;
result = 31 * result + (getContent() != null ? getContent().hashCode() : 0);
result = 31 * result + getType();
return result;
}
}
package com.dayu.livemodule.xiaozhibo.common.msg;
/**
* Module: TCSimpleUserInfo
* <p>
* Function: 用户基本信息封装
*/
public class TCSimpleUserInfo {
public String userid; // userId
public String nickname; // 昵称
public String avatar; // 头像链接
public TCSimpleUserInfo(String userId, String nickname, String avatar) {
this.userid = userId;
this.nickname = nickname;
this.avatar = avatar;
}
}
package com.dayu.livemodule.xiaozhibo.common.net;
import android.text.TextUtils;
import android.util.Log;
import com.dayu.livemodule.xiaozhibo.TCGlobalConfig;
import com.dayu.livemodule.xiaozhibo.common.utils.TCUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* Module: TCHTTPMgr
* <p>
* Function: 专门用来请求小直播后台的网络请求工具类。
* <p>
* 1. APP 基于 OKHTTP 封装了网络请求工具模块,若您不想使用 OKHTTP 仅需修改此类即可。
* <p>
* 2. 封装了向小直播后台发起请求的方法 {@link TCHTTPMgr#requestWithSign(String, JSONObject, Callback)}
* 该方法会自动带上根据 token 以及 userId 生成的 userSign 以保证安全。
*/
public class TCHTTPMgr {
private static final String TAG = "TCHTTPMgr";
private OkHttpClient mOkHTTPClient;
private String mUserId, mToken;
public static final class TCHTTPClientHolder {
static TCHTTPMgr INSTANCE = new TCHTTPMgr();
}
public static final TCHTTPMgr getInstance() {
return TCHTTPClientHolder.INSTANCE;
}
private TCHTTPMgr() {
mOkHTTPClient = new OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
}
public void setUserIdAndToken(String userId, String token) {
mUserId = userId;
mToken = token;
}
/**
* 一般性的网络请求
* <p>
* 项目中可能需要网络请求
*
* @param url
* @param body
* @param callback
*/
public void request(String url, JSONObject body, Callback callback) {
Log.i(TAG, "request: url = " + url + " body = " + (body != null ? body.toString() : ""));
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), body != null ? body.toString() : ""))
.build();
mOkHTTPClient.newCall(request).enqueue(new HttpCallback(callback));
}
/**
* 向小直播后台发起网络请求
* <p>
* 对于小直播后台的请求,我们会对访问进行鉴权,保证后台的安全性。
* <p>
* 这里会根据 token 以及 userId 生成 userSign {@link TCHTTPMgr#getRequestSig(JSONObject)} 用于鉴权。
*
* @param url
* @param body
* @param callback
*/
public void requestWithSign(String url, JSONObject body, Callback callback) {
if (TextUtils.isEmpty(TCGlobalConfig.APP_SVR_URL)) {
if (callback != null) {
callback.onFailure(-999, "没有填写后台地址,此功能暂不支持.");
Log.e(TAG, "requestWithSign: token or userId can't be null.");
}
return;
}
if (TextUtils.isEmpty(mToken) || TextUtils.isEmpty(mUserId)) {
if (callback != null) {
callback.onFailure(-1, "token or userId can't be null.");
Log.e(TAG, "requestWithSign: token or userId can't be null.");
}
return;
}
try {
String strBody = body.put("userid", mUserId)
.put("timestamp", System.currentTimeMillis() / 1000)
.put("expires", 10)
.toString();
String sig = getRequestSig(body);
Log.i(TAG, "requestWithSign: url = " + url + " body = " + strBody + " sign = " + sig);
Request request = new Request.Builder()
.url(url)
.addHeader("Liteav-Sig", sig)
.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), strBody))
.build();
mOkHTTPClient.newCall(request).enqueue(new XZBHttpCallback(callback));
} catch (JSONException e) {
e.printStackTrace();
}
}
/**
* 计算请求的 Sign,用于确认是合法用户访问。
*
* @param body
* @return
*/
public String getRequestSig(JSONObject body) {
String strBody = null;
try {
strBody = body.put("userid", mUserId)
.put("timestamp", System.currentTimeMillis() / 1000)
.put("expires", 10)
.toString();
} catch (JSONException e) {
e.printStackTrace();
}
String sig = TCUtils.md5(mToken + TCUtils.md5(strBody));
return sig;
}
/**
* /////////////////////////////////////////////////////////////////////////////////
* //
* // 网络请求回调
* //
* /////////////////////////////////////////////////////////////////////////////////
*/
/**
* 考虑到您项目中可能并不是使用 okHTTP 所以我们特意对callback进行了一层封装
* <p>
* 这样子你可以仅仅修改 @{@link TCHTTPMgr} 内的代码,即可完整网络模块的改造。
*/
public interface Callback {
/**
* 登录成功
*/
void onSuccess(JSONObject data);
/**
* 登录失败
*
* @param code 错误码
* @param msg 错误信息
*/
void onFailure(int code, final String msg);
}
/**
* 专用用于解析小直播请求的callback
*/
private static class HttpCallback implements okhttp3.Callback {
private Callback callback;
public HttpCallback(Callback callback) {
this.callback = callback;
}
@Override
public void onFailure(Call call, IOException e) {
if (callback != null) {
callback.onFailure(-1, " requestWithSign failure");
}
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String body = response.body().string();
Log.i(TAG, "HttpCallback : onResponse: body = " + body);
JSONObject jsonObject = null;
int code = -1;
try {
jsonObject = new JSONObject(body);
code = 0;
} catch (JSONException e) {
code = -1;
}
if (code == 0) {
if (callback != null) callback.onSuccess(jsonObject);
} else {
if (callback != null) callback.onFailure(code, "server error.");
}
}
}
/**
* 专用用于解析小直播请求的callback
*/
private static class XZBHttpCallback implements okhttp3.Callback {
private Callback callback;
public XZBHttpCallback(Callback callback) {
this.callback = callback;
}
@Override
public void onFailure(Call call, IOException e) {
if (callback != null) {
callback.onFailure(-1, " requestWithSign failure");
}
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String body = response.body().string();
Log.i(TAG, "XZBHttpCallback : onResponse: body = " + body);
JSONObject jsonObject = null;
int code = -1;
String message = "";
JSONObject data = null;
try {
jsonObject = new JSONObject(body);
code = jsonObject.getInt("code");
message = jsonObject.getString("message");
data = jsonObject.optJSONObject("data");
} catch (JSONException e) {
e.printStackTrace();
}
if (code == 200) {
if (callback != null) callback.onSuccess(data);
} else {
if (callback != null) callback.onFailure(code, message);
}
}
}
}
package com.dayu.livemodule.xiaozhibo.common.report;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import com.dayu.livemodule.xiaozhibo.common.net.TCHTTPMgr;
import com.dayu.livemodule.xiaozhibo.common.utils.TCConstants;
import com.dayu.livemodule.xiaozhibo.login.TCUserMgr;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Module: TCELKReportMgr
*
* Function: 小直播 APP 用于做数据上报统计使用的。 您的 APP 可以直接移除此模块,或者保持不动;不会影响 APP 的正常功能。
*
*/
public class TCELKReportMgr {
// 小直播做统计用的,您可以不用关心
private static final String DEFAULT_ELK_HOST = "";
private static final String TAG = "TCELKReportMgr";
private Context mContext;
private String mAppName;
private String mPackageName;
/**
* 用于保证单例
*/
private static final class TCELKReportMgrHolder {
public static final TCELKReportMgr INSTANCE = new TCELKReportMgr();
}
public static final TCELKReportMgr getInstance() {
return TCELKReportMgrHolder.INSTANCE;
}
private TCELKReportMgr() {
}
/**
* 初始化
*
* @param context
*/
public void init(Context context) {
if (context == null) return;
mContext = context.getApplicationContext();
mAppName = getAPPName();
mPackageName = getPackageName();
}
/**
* 监听 Activity 的声明周期
*
* @param application
*/
public void registerActivityCallback(Application application) {
application.registerActivityLifecycleCallbacks(new ELKActivityLifecycleCallbacks());
}
/**
* 小直播APP 数据上报使用的,用于 Demo 数据收集,您可以不用关心。
*
* @param action
* @param userName
* @param code
* @param errorMsg
* @param callback
*/
public void reportELK(String action, String userName, long code, String errorMsg, TCHTTPMgr.Callback callback) {
if (TextUtils.isEmpty(DEFAULT_ELK_HOST)) {
// 对外的发布的源码版本会将 ELK 的上报拿掉。
return;
}
Log.i(TAG, "reportELK: action = " + action + " userName = " + userName + " code = " + code);
String reqUrl = DEFAULT_ELK_HOST;
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("action", action);
jsonObject.put("action_result_code", code);
jsonObject.put("action_result_msg", errorMsg);
jsonObject.put("type", "xiaozhibo");
jsonObject.put("bussiness", "xiaozhibo");
jsonObject.put("userName", userName);
jsonObject.put("platform", "android");
if (mAppName != null) {
jsonObject.put("appname", mAppName);
}
if (mPackageName != null) {
jsonObject.put("appidentifier", mPackageName);
}
TCHTTPMgr.getInstance().request(reqUrl, jsonObject, null);
} catch (JSONException e) {
e.printStackTrace();
}
}
/**
* 获取当前 APP 的名字
*
* @return
*/
private String getAPPName() {
ApplicationInfo applicationInfo = mContext.getApplicationInfo();
int stringId = applicationInfo.labelRes;
return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : mContext.getString(stringId);
}
/**
* 获取当前 APP 的包名
*
* @return
*/
private String getPackageName() {
PackageInfo info;
String packagename = "";
if (this != null) {
try {
info = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
// 当前版本的包名
packagename = info.packageName;
} catch (Exception e) {
e.printStackTrace();
}
}
return packagename;
}
/**
* ELK 数据上报用于监听 Activity 生命周期的回调
*
* 主要是用于统计 APP 的使用体验时长。
*/
private static class ELKActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
private int foregroundActivities;
private boolean isChangingConfiguration;
private long time;
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
foregroundActivities++;
if (foregroundActivities == 1 && !isChangingConfiguration) {
// 应用进入前台
time = System.currentTimeMillis();
}
isChangingConfiguration = false;
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
foregroundActivities--;
if (foregroundActivities == 0) {
// 应用切入后台
long bgTime = System.currentTimeMillis();
long diff = (bgTime - time) / 1000 ;
TCELKReportMgr.getInstance().reportELK(TCConstants.ELK_ACTION_STAY_TIME, TCUserMgr.getInstance().getUserId(), diff, "App体验时长", null);
}
isChangingConfiguration = activity.isChangingConfigurations();
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}
}
package com.dayu.livemodule.xiaozhibo.common.ui;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.widget.TextView;
import com.dayu.livemodule.R;
import com.dayu.livemodule.roomutil.commondef.MLVBCommonDef;
/**
* Created by Administrator on 2016/9/26.
*/
public class ErrorDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
int errorCode = getArguments().getInt("errorCode");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.ConfirmDialogStyle)
.setCancelable(true)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
getActivity().finish();
}
});
if (errorCode == MLVBCommonDef.LiveRoomErrorCode.ERROR_LICENSE_INVALID) {
String errInfo = "License 校验失败";
int start = (errInfo + " 详情请点击[").length();
int end = (errInfo + " 详情请点击[License 使用指南").length();
SpannableStringBuilder spannableStrBuidler = new SpannableStringBuilder(errInfo + " 详情请点击[License 使用指南]");
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
Uri content_url = Uri.parse("https://cloud.tencent.com/document/product/454/34750");
intent.setData(content_url);
startActivity(intent);
}
};
spannableStrBuidler.setSpan(new ForegroundColorSpan(Color.BLUE), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableStrBuidler.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
TextView tv = new TextView(this.getActivity());
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setText(spannableStrBuidler);
tv.setPadding(20, 50, 20, 0);
builder.setView(tv).setTitle("推流失败");
} else {
String errInfo = getArguments().getString("errorMsg");
builder.setTitle(errInfo);
}
AlertDialog alertDialog = builder.create();
alertDialog.setCancelable(false);
alertDialog.setCanceledOnTouchOutside(false);
return alertDialog;
}
}
package com.dayu.livemodule.xiaozhibo.common.upload;
/**
*/
import android.text.TextUtils;
import com.dayu.livemodule.xiaozhibo.TCGlobalConfig;
import com.dayu.livemodule.xiaozhibo.common.net.TCHTTPMgr;
import com.tencent.qcloud.core.auth.BasicLifecycleCredentialProvider;
import com.tencent.qcloud.core.auth.BasicQCloudCredentials;
import com.tencent.qcloud.core.auth.QCloudLifecycleCredentials;
import com.tencent.qcloud.core.common.QCloudClientException;
import com.tencent.rtmp.TXLog;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Module: TCCOSNetworkCredentialProvider
*
* Function: COS 存储需要从服务器获取签名 signKey keyTime
*
**/
public class TCCOSNetworkCredentialProvider extends BasicLifecycleCredentialProvider {
static final String TAG = "TCCOSNetworkCredentialProvider";
String secretId = null;
public TCCOSNetworkCredentialProvider(String secretId) {
this.secretId = secretId;
}
@Override
protected QCloudLifecycleCredentials fetchNewCredentials() throws QCloudClientException {
String signKey = null;
String keyTime = null;
try {
JSONObject json = new JSONObject();
String sig = TCHTTPMgr.getInstance().getRequestSig(json);
String jsonstr = json.toString();
URL signKeyUrl = new URL(TCGlobalConfig.APP_SVR_URL+"/get_cos_sign");
HttpURLConnection urlConnection = (HttpURLConnection) signKeyUrl.openConnection();
urlConnection.setConnectTimeout(3000);
urlConnection.setUseCaches(false);
urlConnection.setReadTimeout(3000);
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Liteav-Sig",sig);
urlConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
urlConnection.connect();
OutputStream out = urlConnection.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
bw.write(jsonstr);
bw.flush();
out.close();
bw.close();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream in = urlConnection.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = br.readLine()) != null) {
buffer.append(str);
}
in.close();
br.close();
JSONObject rjson = new JSONObject(buffer.toString());
if (rjson.has("code") && rjson.getInt("code") == 200) {
JSONObject retData = rjson.optJSONObject("data");
if (retData.has("signKey")) {
signKey = retData.getString("signKey");
}
if (retData.has("keyTime")) {
keyTime = retData.getString("keyTime");
}
TXLog.w(TAG, "xzb_process: get_cos_sign success");
} else {
TXLog.w(TAG, "xzb_process: get_cos_sign failure");
}
}
urlConnection.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
if (!TextUtils.isEmpty(signKey) && !TextUtils.isEmpty(keyTime))
return new BasicQCloudCredentials(this.secretId, signKey, keyTime);
else
return null;
}
}
package com.dayu.livemodule.xiaozhibo.common.upload;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import com.dayu.livemodule.xiaozhibo.TCGlobalConfig;
import com.dayu.livemodule.xiaozhibo.login.TCUserMgr;
import com.tencent.cos.xml.CosXmlService;
import com.tencent.cos.xml.CosXmlServiceConfig;
import com.tencent.cos.xml.exception.CosXmlClientException;
import com.tencent.cos.xml.exception.CosXmlServiceException;
import com.tencent.cos.xml.listener.CosXmlProgressListener;
import com.tencent.cos.xml.listener.CosXmlResultListener;
import com.tencent.cos.xml.model.CosXmlRequest;
import com.tencent.cos.xml.model.CosXmlResult;
import com.tencent.cos.xml.model.object.PutObjectACLRequest;
import com.tencent.cos.xml.model.object.PutObjectRequest;
/**
* Module: TCUploadHelper
*
* Function: 上传图片到 Cos 的工具类
*
* 直播开播之前,可以设置自己的封面,所以我们需要将图片保存到 cos 存储,提供给观看端查看。
*
* 腾讯云对于 COS 存储服务提供了额外的 SDK,他不属于 LiteAVSDK 的组成部分,该工具仅提供一个简单用法;
*
* COS SDK 相关服务的开通可以参考此篇文章:
* https://cloud.tencent.com/document/product/454/15187#2.-.E5.BC.80.E9.80.9A.E5.AF.B9.E8.B1.A1.E5.AD.98.E5.82.A8.E6.9C.8D.E5.8A.A1
*
**/
public class TCUploadHelper {
private static final String TAG = "TCUploadHelper";
private final static int MESSAGE_CODE_RESULT = 1;
public final static int UPLOAD_RESULT_SUCCESS = 0;
public final static int UPLOAD_RESULT_FAIL = -1;
private OnUploadListener mCallbackListener;
private Handler mMainHandler;
private CosXmlService mCosService;
public TCUploadHelper(final Context context, OnUploadListener listener) {
mCallbackListener = listener;
mMainHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_CODE_RESULT:
if (mCallbackListener != null) {
}
break;
default:
break;
}
return false;
}
});
final TCUserMgr.CosInfo cosInfo = TCUserMgr.getInstance().getCosInfo();
CosXmlServiceConfig cosXmlServiceConfig = new CosXmlServiceConfig.Builder()
.setAppidAndRegion(cosInfo.appID, cosInfo.region)
.setDebuggable(true)
.builder();
mCosService = new CosXmlService(context, cosXmlServiceConfig,
new TCCOSNetworkCredentialProvider(cosInfo.secretID));
}
private String createNetUrl() {
return "/" + TCUserMgr.getInstance().getUserId() + "/" + System.currentTimeMillis();
}
public void uploadPic(final String path) {
Log.d(TAG,"uploadPic do upload path:"+path);
if (TextUtils.isEmpty(TCGlobalConfig.APP_SVR_URL)) {
mMainHandler.post(new Runnable() {
@Override
public void run() {
if (mCallbackListener != null) {
mCallbackListener.onUploadResult(UPLOAD_RESULT_FAIL, "没有填写后台地址,此功能暂不支持.");
}
}
});
return;
}
final String netUrl = createNetUrl();
final TCUserMgr.CosInfo cosInfo = TCUserMgr.getInstance().getCosInfo();
PutObjectRequest putObjectRequest = new PutObjectRequest(cosInfo.bucket, netUrl, path);
putObjectRequest.setProgressListener(new CosXmlProgressListener() {
@Override
public void onProgress(long progress, long max) {
}
});
putObjectRequest.setSign(600,null,null);
mCosService.putObjectAsync(putObjectRequest, new CosXmlResultListener() {
@Override
public void onSuccess(CosXmlRequest cosXmlRequest, CosXmlResult cosXmlResult) {
//修改访问权限为公开
PutObjectACLRequest putObjectACLRequest = new PutObjectACLRequest(cosInfo.bucket, netUrl);
putObjectACLRequest.setXCOSACL("public-read");
putObjectACLRequest.setSign(600,null,null);
try {
mCosService.putObjectACL(putObjectACLRequest);
} catch (CosXmlServiceException e) {
e.printStackTrace();
} catch (CosXmlClientException e) {
e.printStackTrace();
}
final TCUserMgr.CosInfo cosInfo = TCUserMgr.getInstance().getCosInfo();
final String accessUrl = "http://" + cosXmlRequest.getHost(cosInfo.appID, cosInfo.region) + cosXmlRequest.getPath();
Log.d(TAG,"uploadPic do upload sucess, url:" + accessUrl);
mMainHandler.post(new Runnable() {
@Override
public void run() {
if (mCallbackListener != null) {
mCallbackListener.onUploadResult(UPLOAD_RESULT_SUCCESS, accessUrl);
}
}
});
}
@Override
public void onFail(CosXmlRequest cosXmlRequest, CosXmlClientException qcloudException, CosXmlServiceException qcloudServiceException) {
final StringBuilder stringBuilder = new StringBuilder();
if(qcloudException != null){
stringBuilder.append(qcloudException.getMessage());
}else {
stringBuilder.append(qcloudServiceException.toString());
}
Log.e(TAG, "uploadPic do upload fail, msg:" + stringBuilder.toString());
mMainHandler.post(new Runnable() {
@Override
public void run() {
if (mCallbackListener != null) {
mCallbackListener.onUploadResult(UPLOAD_RESULT_FAIL, stringBuilder.toString());
}
}
});
}
});
}
public interface OnUploadListener {
void onUploadResult(int code, String url);
}
}
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 sign in to comment