布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.tencent.rtmp.ui.TXCloudVideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:visibility="gone"/>
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.bwie.chuliangliang.suixinbo_demo.mvp.ui.view.GiftView
android:id="@+id/gift_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true" />
<com.itheima.danmaku.ui.widget.DanmakuView
android:id="@+id/sv_danmaku"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/btn_addGift"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_margin="20dp"
android:background="@mipmap/xin"
android:padding="14dp"
android:textSize="18sp" />
<Button
android:id="@+id/btn_xiangji"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_alignTop="@+id/btn_addGift"
android:layout_marginStart="86dp"
android:background="@mipmap/xiangji"
android:layout_alignParentLeft="true"
android:layout_marginLeft="86dp" />
<Button
android:id="@+id/btn_shan"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_alignTop="@+id/btn_addGift"
android:layout_marginStart="156dp"
android:background="@mipmap/shan"
android:layout_alignParentLeft="true"
android:layout_marginLeft="156dp" />
<Button
android:id="@+id/button3"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_alignTop="@+id/btn_addGift"
android:layout_marginEnd="108dp"
android:background="@mipmap/meiyan"
android:layout_alignParentRight="true"
android:layout_marginRight="90dp" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:layout_marginBottom="88dp"
android:max="9"
android:visibility="invisible"
android:layout_alignParentLeft="true" />
<Button
android:id="@+id/btn_start"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_alignTop="@+id/btn_addGift"
android:layout_marginEnd="35dp"
android:background="@mipmap/xiaoxi"
android:layout_alignParentRight="true"
android:layout_marginRight="35dp" />
//卡片布局
<android.support.v7.widget.CardView
android:layout_width="140dp"
android:layout_height="60dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
app:cardCornerRadius="16dp"
app:cardUseCompatPadding="true"
app:cardPreventCornerOverlap="false"
app:cardBackgroundColor="@android:color/transparent"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="10dp"
app:placeholderImage="@drawable/tou"
app:roundedCornerRadius="35dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_gravity="center"
android:textColor="#fff"
android:text="11111"/>
</LinearLayout>
</android.support.v7.widget.CardView>
<Switch
android:id="@+id/switch2"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="15dp"
android:layout_marginTop="11dp"
android:switchMinWidth="20dp"
android:text="弹幕"
android:textOff="off"
android:textOn="on"
android:textColor="#fff"
android:thumb="@drawable/thumb"
android:track="@drawable/track"
android:layout_alignParentRight="true"
android:layout_marginRight="15dp" />
<android.support.v7.widget.CardView
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_marginBottom="119dp"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="16dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true"
android:visibility="invisible"
android:layout_alignParentRight="true">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"></ListView>
</android.support.v7.widget.CardView>
</RelativeLayout>
Activity类
public class StartLivesActivity extends BaseActivity<StartLivesPresenter> implements StartLivesContract.View, View.OnClickListener {
@BindView(R.id.surfaceView)
SurfaceView surfaceView;
@BindView(R.id.video_view)
TXCloudVideoView videoView;
@BindView(R.id.btn_addGift)
Button btnAddGift;
@BindView(R.id.gift_view)
GiftView giftView;
@BindView(R.id.btn_xiangji)
Button btnXiangji;
@BindView(R.id.btn_shan)
Button btnShan;
@BindView(R.id.button3)
Button button3;
@BindView(R.id.seekBar)
SeekBar seekBar;
@BindView(R.id.btn_start)
Button btnStart;
@BindView(R.id.sv_danmaku)
DanmakuView svDanmaku;
@BindView(R.id.my_image_view)
SimpleDraweeView myImageView;
@BindView(R.id.switch2)
Switch switch2;
@BindView(R.id.listView)
ListView listView;
@BindView(R.id.cardView)
CardView cardView;
private TXLivePlayer mLivePlayer;
private TXLivePusher mLivePusher;
private TXLivePushConfig mLivePushConfig;
private PopupWindow popupWindow;
private View view;
private DanmakuView danmakuView;
private EditText edit_text;
private Button but_text;
private List<MyBean> list;
private MyAdapter myAdapter;
private MyBean myBean;
@Override
public void setupActivityComponent(@NonNull AppComponent appComponent) {
DaggerStartLivesComponent //如找不到该类,请编译一下项目
.builder()
.appComponent(appComponent)
.startLivesModule(new StartLivesModule(this))
.build()
.inject(this);
}
@Override
public int initView(@Nullable Bundle savedInstanceState) {
return R.layout.activity_start_lives; //如果你不需要框架帮你设置 setContentView(id) 需要自行设置,请返回 0
}
//沉浸式
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && Build.VERSION.SDK_INT >= 19) {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
}
@Override
public void initData(@Nullable Bundle savedInstanceState) {
//popupWindow
view = View.inflate(this, R.layout.popup, null);
edit_text = view.findViewById(R.id.edit_text);
but_text = view.findViewById(R.id.but_text);
but_text.setOnClickListener(this);
popupWindow = new PopupWindow(view, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, true);
popupWindow.setBackgroundDrawable(new BitmapDrawable());
popupWindow.setFocusable(true);
popupWindow.setOutsideTouchable(true);
list = new ArrayList<>();
surfaceView.getHolder().setKeepScreenOn(true);
surfaceView.getHolder().addCallback(new SurfaceViewLis());
//BiBiLiLi弹幕
switch2.setChecked(false);
switch2.setSwitchTextAppearance(this, R.style.s_false);
switch2.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
switch2.setSwitchTextAppearance(StartLivesActivity.this, R.style.s_true);
DanmakuContext context = DanmakuContext.create();
danmakuView = findViewById(R.id.sv_danmaku);
if (danmakuView != null) {
//根据数据源转化成弹幕解析器
BaseDanmakuParser mParser = DanmakuUtils.createParser(StartLivesActivity.this.getResources().openRawResource(R.raw.comments));
//设置回调监听
danmakuView.setCallback(new DrawHandler.Callback() {
@Override
public void updateTimer(DanmakuTimer timer) {
}
@Override
public void drawingFinished() {
}
@Override
public void danmakuShown(BaseDanmaku danmaku) {
}
//页面准备完毕就开始播放弹幕
@Override
public void prepared() {
danmakuView.start();
}
});
//准备数据
danmakuView.prepare(mParser, context);
//是否显示fps
danmakuView.showFPS(false);
//绘制缓存
danmakuView.enableDanmakuDrawingCache(true);
}
} else {
switch2.setSwitchTextAppearance(StartLivesActivity.this, R.style.s_false);
danmakuView.stop();
}
}
});
}
@Override
public void showLoading() {
}
@Override
public void hideLoading() {
}
@Override
public void showMessage(@NonNull String message) {
checkNotNull(message);
ArmsUtils.snackbarText(message);
}
@Override
public void launchActivity(@NonNull Intent intent) {
checkNotNull(intent);
ArmsUtils.startActivity(intent);
}
@Override
public void killMyself() {
finish();
}
@OnClick({R.id.btn_addGift, R.id.btn_xiangji, R.id.btn_shan, R.id.button3, R.id.btn_start})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btn_addGift:
giftView.addGifts();
break;
case R.id.btn_xiangji:
// 默认是前置摄像头
mLivePusher.switchCamera();
break;
case R.id.btn_shan:
boolean mFlashTurnOn = false;
if (!mLivePusher.turnOnFlashLight(mFlashTurnOn)) {
Toast.makeText(StartLivesActivity.this.getApplicationContext(),
"打开闪光灯失败:绝大部分手机不支持前置闪光灯!", Toast.LENGTH_SHORT).show();
}
break;
case R.id.button3:
seekBar.setVisibility(View.VISIBLE);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//配置:包括美颜、设置直播质量等等内容
//画质
mLivePusher.setVideoQuality(VIDEO_QUALITY_HIGH_DEFINITION, false, false);
//美颜
//style 磨皮风格: 0:光滑 1:自然 2:朦胧
//beautyLevel 磨皮等级: 取值为 0-9.取值为 0 时代表关闭美颜效果.默认值: 0,即关闭美颜效果.
//whiteningLevel 美白等级: 取值为 0-9.取值为 0 时代表关闭美白效果.默认值: 0,即关闭美白效果.
//ruddyLevel 红润等级: 取值为 0-9.取值为 0 时代表关闭美白效果.默认值: 0,即关闭美白效果.
mLivePusher.setBeautyFilter(1, progress, progress, progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
//滤镜程度
/*Bitmap bmp = null;
bmp = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
if (mLivePusher != null) {
mLivePusher.setFilter(bmp);
mLivePusher.setSpecialRatio(0.1f);
}*/
break;
case R.id.btn_start:
popupWindow.showAtLocation(view, Gravity.BOTTOM, 0, 120);
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: add setContentView(...) invocation
ButterKnife.bind(this);
}
@Override
public void onClick(View v) {
//加载聊天内容
cardView.setVisibility(View.VISIBLE);
myBean = new MyBean();
myBean.setName(edit_text.getText().toString());
list.add(myBean);
myAdapter = new MyAdapter(StartLivesActivity.this, list);
listView.setAdapter(myAdapter);
}
private class SurfaceViewLis implements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder holder) {
String sdkver = TXLiveBase.getSDKVersionStr();
Log.e("liteavsdk", "liteav sdk version is : " + sdkver);
mLivePusher = new TXLivePusher(StartLivesActivity.this);
mLivePushConfig = new TXLivePushConfig();
//直播加速
boolean mHWVideoEncode = true;
if (mHWVideoEncode) {
if (mLivePushConfig != null) {
if (Build.VERSION.SDK_INT < 18) {
Toast.makeText(getApplicationContext(), "硬件加速失败,当前手机 API 级别过低(最低 18)",
Toast.LENGTH_SHORT).show();
mHWVideoEncode = false;
}
}
}
mLivePushConfig.setHardwareAcceleration(mHWVideoEncode ?
TXLiveConstants.ENCODE_VIDEO_HARDWARE : TXLiveConstants.ENCODE_VIDEO_SOFTWARE);
mLivePusher.setConfig(mLivePushConfig);
mLivePusher.setMirror(true);
String rtmpUrl = "rtmp://33071.livepush.myqcloud.com/live/33071_3cb8e8198c?bizid=33071&txSecret=ff430db14b6c187c19aabf383e0ef2e5&txTime=5BBF737F";
mLivePusher.startPusher(rtmpUrl);
mLivePusher.startCameraPreview(videoView);
//创建 player 对象
mLivePlayer = new TXLivePlayer(StartLivesActivity.this);
//关键 player 对象与界面 view
mLivePlayer.setPlayerView(videoView);
String flvUrl = "http://33071.liveplay.myqcloud.com/live/33071_3cb8e8198c.flv";
mLivePlayer.startPlay(flvUrl, TXLivePlayer.PLAY_TYPE_LIVE_FLV); //推荐 FLV
// 设置填充模式
mLivePlayer.setRenderMode(TXLiveConstants.RENDER_MODE_FULL_FILL_SCREEN);
// 设置画面渲染方向
mLivePlayer.setRenderRotation(TXLiveConstants.RENDER_ROTATION_PORTRAIT);
TXLivePlayConfig mPlayConfig = new TXLivePlayConfig();
mPlayConfig.setAutoAdjustCacheTime(true);
mPlayConfig.setMinAutoAdjustCacheTime(1);
mPlayConfig.setMaxAutoAdjustCacheTime(5);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mLivePlayer.stopPlay(true);
videoView.onDestroy();
}
}
}
popup弹出输入框
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<EditText
android:id="@+id/edit_text"
android:layout_width="500dp"
android:layout_height="30dp"
android:background="@drawable/edit_bian"
android:hint="请发表内容"
android:textColor="#fff"
android:layout_weight="9"/>
<Button
android:id="@+id/but_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="发表"/>
</LinearLayout>
自定义点赞飘心效果
public class GiftView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private ArrayList<GiftBean> mDatas;
private ArrayList<GiftBean> newest;
private int mWidth, mHeight;
private MyThread myThread;
public GiftView(Context context) {
super(context);
}
public GiftView(Context context, AttributeSet attrs) {
super(context, attrs);
mHolder = this.getHolder();
setZOrderOnTop(true); // necessary
mHolder.setFormat(PixelFormat.TRANSPARENT);
mHolder.addCallback(this);
myThread = new MyThread();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = w;
mHeight = h;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mDatas = new ArrayList<>();
newest = new ArrayList<>();
Collections.synchronizedList(newest);//这样就可以多线程访问了
//刚进来就让他先刷一波
for (int i = 0; i < 10; i++) {
Point[] randomPoint = createRandomPoint();
GiftBean bean = new GiftBean(randomPoint);
mDatas.add(bean);
}
if (!myThread.isAlive()) {
myThread = new MyThread();
myThread.start();
}
}
//添加新的小礼物
public void addGifts() {
for (int i = 0; i < Math.random() * 5; i++) {
Point[] randomPoint = createRandomPoint();
GiftBean bean = new GiftBean(randomPoint);
newest.add(bean);
}
if (!myThread.isAlive()) {
myThread = new MyThread();
myThread.start();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.i("Alex" , "surfaceChanged");
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i("Alex", "surfaceDestroyed");
}
class MyThread extends Thread {
private Bitmap mBitmap;
private Paint paint;
public MyThread() {
paint = new Paint();
//这个很重要,要把之前的清空
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
//小礼物就长这样
mBitmap = getBitmapFromDrawable(R.mipmap.ic_launcher);
}
@Override
public void run() {
while (mDatas.size() > 0 || newest.size() > 0) {
Canvas canvas = mHolder.lockCanvas();
if (canvas == null) {
mDatas.clear();
newest.clear();
return;
}
//需要把爬出去的小礼物给清空
List<GiftBean> delete = new ArrayList<>();
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
Canvas canvas_temp = new Canvas(bitmap);
//这里新创建一个canvas起到了双缓冲的作用,在这里其实作用不大,但是这个用法值得学习
//双缓冲是一种在内存中保留屏幕的副本或缓存的技术,先将所有图形都加载在内存中,然后一起绘制到屏幕上,避免了直接在屏幕上绘图时出现的明显闪烁。
//先看看有没有刚加进来的小礼物
mDatas.addAll(newest);
newest.clear();
for (GiftBean giftSimple : mDatas) {
giftSimple.time += 1;
float x = 0.5f * giftSimple.a * giftSimple.time * giftSimple.time;
giftSimple.t = x / mHeight;
giftSimple.alpha = 1f - giftSimple.t;
if (giftSimple.t >= 1f) {
delete.add(giftSimple);
continue;
}
Point point = evaluate(giftSimple.t, giftSimple.pointStart, giftSimple.pointFirst, giftSimple.pointSecond, giftSimple.pointEnd);
Paint paint_temp = new Paint();
paint_temp.setAlpha((int) (giftSimple.alpha * 255));
canvas_temp.drawBitmap(mBitmap, point.x, point.y, paint_temp);
}
canvas.drawBitmap(bitmap, 0, 0, paint);
SystemClock.sleep(10);
mHolder.unlockCanvasAndPost(canvas);
mDatas.removeAll(delete);//把爬出去的小礼物都给清空
}
}
}
protected Bitmap getBitmapFromDrawable(@DrawableRes int resId) {
BitmapDrawable drawable = (BitmapDrawable) getContext().getResources().getDrawable(resId);
Bitmap bitmap = drawable.getBitmap();
return Bitmap.createScaledBitmap(bitmap, 50, 50, false);
}
//利用三阶贝塞尔曲线公式算出中间点坐标,不知道公式的可以上网了解下,
public Point evaluate(float t, Point startValue, Point first, Point second, Point endValue) { //利用三阶贝塞尔曲线公式算出中间点坐标
int x = (int) (startValue.x * Math.pow((1 - t), 3) + 3 * first.x * t * Math.pow((1 - t), 2) + 3 * second.x * Math.pow(t, 2) * (1 - t) + endValue.x * Math.pow(t, 3));
int y = (int) (startValue.y * Math.pow((1 - t), 3) + 3 * first.y * t * Math.pow((1 - t), 2) + 3 * second.y * Math.pow(t, 2) * (1 - t) + endValue.y * Math.pow(t, 3));
return new Point(x, y);
}
//随机生成小礼物的四个点
private Point[] createRandomPoint() {
Point point_start = new Point(mWidth / 8, mHeight);//我希望小礼物都从控件的底部中间出来,都在相同的地方出生,然后人生就瞎跑吧。
Point point_end = new Point((int) (Math.random() * mWidth), 0);//这个没什么好说,从哪出去都行,反正都在控件的最顶部
//随机生成的两个控制点
Point point_first = new Point((int) (Math.random() * (mWidth / 2)), (int) (Math.random() * (mHeight / 2) + mHeight / 2));
Point point_second = new Point((int) (Math.random() * (mWidth / 2) + mWidth / 2), (int) (Math.random() * (mHeight / 2)));
//下面的代码我就想表达一个意思,小礼物出生后我不想让所有的都先往左跑,或者都往又跑,而是随机的,这个方法有点烂,大家轻喷
double leftOrRight = Math.random()-0.5d;
if (leftOrRight>0){
point_first.x = point_first.x+mWidth/2;
point_second.x = point_second.x-mWidth/2;
}
return new Point[]{point_start, point_first, point_second, point_end};
}
}
飘心Bean类
public class GiftBean {
public float alpha = 1f;//小礼物会慢慢的变透明,0<=alpha<=1
public Point pointStart;//开始点
public Point pointEnd;//结束
public Point pointFirst;//我么采用三阶贝塞尔曲线,这是第一个参考点
public Point pointSecond;//这是第二个参考点
public float a;//加速度,我们并不希望小礼物慢吞吞的运动,更不希望小礼物一闪而过,大家也可以用一下插值器,
//在这我就用大家最熟悉的加速度了,毕竟大家的物理都不是白学的
public int time;
public float t;//贝塞尔曲线的变量 并且 0<=t<=1
public GiftBean(Point[] randomPoint) {
//随机生成的加速度,毕竟偶们不希望每个小礼物都有相同的速度,看起来像部队一样,不够调皮
float random = (float) Math.random();
this.a = Math.min(0.1f,random);
this.a = Math.max(0.075f,a);//我调的比较靠谱的加速度,大家也可以改改试试
this.pointStart = randomPoint[0];
this.pointFirst = randomPoint[1];
this.pointSecond = randomPoint[2];
this.pointEnd = randomPoint[3];
}
}