修复::手机无法正常开播

This commit is contained in:
xiao12feng8 2026-01-05 10:13:59 +08:00
parent 18719d752b
commit 1be8f14f07
8 changed files with 529 additions and 331 deletions

4
.gitignore vendored
View File

@ -45,3 +45,7 @@ archive/
Thumbs.db
**/.idea/
**/.vscode/
android-app/gradle/wrapper/gradle-wrapper.properties
android-app/gradle/wrapper/gradle-wrapper.properties
android-app/gradle/wrapper/gradle-wrapper.properties
android-app/gradle/wrapper/gradle-wrapper.properties

View File

@ -9,7 +9,6 @@ import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.util.Size;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.WindowManager;
@ -28,9 +27,11 @@ import com.example.livestreaming.net.AuthStore;
import com.example.livestreaming.net.CreateRoomRequest;
import com.example.livestreaming.net.Room;
import com.pedro.encoder.input.video.CameraHelper;
import com.pedro.encoder.input.video.CameraOpenException;
import com.pedro.rtmp.utils.ConnectCheckerRtmp;
import com.pedro.rtplibrary.rtmp.RtmpCamera1;
import com.pedro.rtplibrary.rtmp.RtmpCamera2;
import com.pedro.rtplibrary.view.OpenGlView;
import java.util.Locale;
import java.util.Map;
@ -42,7 +43,7 @@ import retrofit2.Response;
/**
* 手机开播界面
* 使用 RootEncoder 进行 RTMP 推流
* 优先使用 Camera2 API (RtmpCamera2)兼容性更好
* 使用 OpenGlView 确保视频编码正常工作
*/
public class BroadcastActivity extends AppCompatActivity implements ConnectCheckerRtmp, SurfaceHolder.Callback {
@ -68,11 +69,11 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
private boolean streamerVerified = false;
private boolean cameraInitialized = false;
// 推流参数 - 平衡画质和流畅度
private static final int VIDEO_WIDTH = 720;
private static final int VIDEO_HEIGHT = 480;
private static final int VIDEO_FPS = 24;
private static final int VIDEO_BITRATE = 1200 * 1024; // 1.2Mbps
// 推流参数 - 使用标准16:9分辨率兼容性更好
private static final int VIDEO_WIDTH = 640;
private static final int VIDEO_HEIGHT = 360; // 标准16:9比例
private static final int VIDEO_FPS = 25; // 标准帧率
private static final int VIDEO_BITRATE = 800 * 1024; // 800kbps更流畅
private static final int AUDIO_BITRATE = 64 * 1024;
private static final int AUDIO_SAMPLE_RATE = 44100;
@ -239,7 +240,8 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
}
private void setupSurface() {
binding.surfaceView.getHolder().addCallback(this);
// OpenGlView 使用 SurfaceHolder.Callback
binding.openGlView.getHolder().addCallback(this);
}
private void checkPermissions() {
@ -285,7 +287,7 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
}
if (cameraInitialized) {
Log.d(TAG, "摄像头已初始化");
Log.d(TAG, "摄像头已初始化,跳过");
return;
}
@ -296,6 +298,8 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
return;
}
// 立即设置标志防止重复初始化
cameraInitialized = true;
Log.d(TAG, "开始初始化摄像头...");
// 延迟初始化确保 Surface 完全准备好
@ -304,6 +308,7 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
initCameraInternal();
} catch (Exception e) {
Log.e(TAG, "摄像头初始化异常: " + e.getMessage(), e);
cameraInitialized = false; // 失败时重置标志
Toast.makeText(this, "摄像头初始化失败", Toast.LENGTH_LONG).show();
}
}, 500);
@ -313,16 +318,40 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
// 优先尝试 Camera2 API (Android 5.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
Log.d(TAG, "尝试使用 Camera2 API...");
rtmpCamera2 = new RtmpCamera2(binding.surfaceView, this);
Log.d(TAG, "尝试使用 Camera2 API + OpenGlView...");
// 关键使用 OpenGlView 构造 RtmpCamera2确保视频编码正常
rtmpCamera2 = new RtmpCamera2(binding.openGlView, this);
// 先开始预览再准备编码器推流时再准备
// 关键正确顺序先准备编码器再开始预览
// 准备音频编码器
boolean audioReady = rtmpCamera2.prepareAudio(AUDIO_BITRATE, AUDIO_SAMPLE_RATE, false);
Log.d(TAG, "Camera2 音频编码器准备: " + audioReady);
// 准备视频编码器 - 尝试多种分辨率
boolean videoReady = rtmpCamera2.prepareVideo(VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_FPS, VIDEO_BITRATE, 2);
Log.d(TAG, "Camera2 视频编码器准备 (640x360): " + videoReady);
if (!videoReady) {
videoReady = rtmpCamera2.prepareVideo(480, 270, 20, 500 * 1024, 2);
Log.d(TAG, "Camera2 视频编码器准备 (480x270): " + videoReady);
}
if (!videoReady) {
videoReady = rtmpCamera2.prepareVideo(320, 240, 15, 300 * 1024, 2);
Log.d(TAG, "Camera2 视频编码器准备 (320x240): " + videoReady);
}
if (!audioReady || !videoReady) {
Log.e(TAG, "Camera2 编码器准备失败,尝试 Camera1");
rtmpCamera2 = null;
} else {
// 编码器准备好后开始预览
String cameraId = isFrontCamera ? "1" : "0";
rtmpCamera2.startPreview(cameraId);
useCamera2 = true;
cameraInitialized = true;
Log.d(TAG, "Camera2 预览已开始");
Log.d(TAG, "Camera2 + OpenGlView 初始化完成,预览已开始");
return;
}
} catch (Exception e) {
Log.w(TAG, "Camera2 初始化失败: " + e.getMessage(), e);
if (rtmpCamera2 != null) {
@ -334,14 +363,33 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
// 回退到 Camera1 API
try {
Log.d(TAG, "尝试使用 Camera1 API...");
rtmpCamera1 = new RtmpCamera1(binding.surfaceView, this);
Log.d(TAG, "尝试使用 Camera1 API + OpenGlView...");
// 关键使用 OpenGlView 构造 RtmpCamera1
rtmpCamera1 = new RtmpCamera1(binding.openGlView, this);
// 关键正确顺序先准备编码器再开始预览
boolean audioReady = rtmpCamera1.prepareAudio(AUDIO_BITRATE, AUDIO_SAMPLE_RATE, false, false, false);
Log.d(TAG, "Camera1 音频编码器准备: " + audioReady);
boolean videoReady = rtmpCamera1.prepareVideo(VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_FPS, VIDEO_BITRATE, 2);
Log.d(TAG, "Camera1 视频编码器准备 (640x360): " + videoReady);
if (!videoReady) {
videoReady = rtmpCamera1.prepareVideo(480, 270, 20, 500 * 1024, 2);
Log.d(TAG, "Camera1 视频编码器准备 (480x270): " + videoReady);
}
if (!audioReady || !videoReady) {
Log.e(TAG, "Camera1 编码器也准备失败");
cameraInitialized = false; // 重置标志
Toast.makeText(this, "编码器初始化失败", Toast.LENGTH_LONG).show();
return;
}
CameraHelper.Facing facing = isFrontCamera ? CameraHelper.Facing.FRONT : CameraHelper.Facing.BACK;
rtmpCamera1.startPreview(facing);
useCamera2 = false;
cameraInitialized = true;
Log.d(TAG, "Camera1 预览已开始");
Log.d(TAG, "Camera1 + OpenGlView 初始化完成,预览已开始");
return;
} catch (Exception e) {
Log.e(TAG, "Camera1 初始化也失败: " + e.getMessage(), e);
@ -351,6 +399,7 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
rtmpCamera1 = null;
}
cameraInitialized = false; // 重置标志
Toast.makeText(this, "摄像头初始化失败,请检查权限或重启应用", Toast.LENGTH_LONG).show();
}
@ -484,53 +533,23 @@ public class BroadcastActivity extends AppCompatActivity implements ConnectCheck
Log.d(TAG, "开始推流到: " + rtmpUrl);
try {
// 编码器已在 initCameraInternal 中准备好直接开始推流
if (useCamera2 && rtmpCamera2 != null) {
Log.d(TAG, "使用 Camera2 API 推流");
// 推流前准备编码器 - 使用优化后的低码率参数
boolean audioReady = rtmpCamera2.prepareAudio(AUDIO_BITRATE, AUDIO_SAMPLE_RATE, false);
// 使用 640x360 低分辨率更流畅
boolean videoReady = rtmpCamera2.prepareVideo(VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_FPS, VIDEO_BITRATE, 0);
Log.d(TAG, "编码器准备: audio=" + audioReady + ", video=" + videoReady);
if (!videoReady) {
// 尝试更低的分辨率
Log.w(TAG, "640x360 失败,尝试 480x270");
videoReady = rtmpCamera2.prepareVideo(480, 270, VIDEO_FPS, VIDEO_BITRATE, 0);
}
if (!videoReady) {
Log.e(TAG, "视频编码器准备失败");
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
Toast.makeText(this, "视频编码器初始化失败", Toast.LENGTH_SHORT).show();
return;
}
if (!rtmpCamera2.isStreaming()) {
rtmpCamera2.startStream(rtmpUrl);
Log.d(TAG, "Camera2 推流已启动");
}
} else if (rtmpCamera1 != null) {
Log.d(TAG, "使用 Camera1 API 推流");
// 推流前准备编码器 - 使用优化参数
boolean audioReady = rtmpCamera1.prepareAudio(AUDIO_BITRATE, AUDIO_SAMPLE_RATE, false, false, false);
boolean videoReady = rtmpCamera1.prepareVideo(VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_FPS, VIDEO_BITRATE, 0);
Log.d(TAG, "编码器准备: audio=" + audioReady + ", video=" + videoReady);
if (!videoReady) {
Log.e(TAG, "视频编码器准备失败");
binding.progressBar.setVisibility(View.GONE);
binding.btnStartLive.setEnabled(true);
Toast.makeText(this, "视频编码器初始化失败", Toast.LENGTH_SHORT).show();
return;
}
if (!rtmpCamera1.isStreaming()) {
rtmpCamera1.startStream(rtmpUrl);
Log.d(TAG, "Camera1 推流已启动");
}
} else {
Log.e(TAG, "没有可用的摄像头推流器");
binding.progressBar.setVisibility(View.GONE);

View File

@ -3,6 +3,7 @@ package com.example.livestreaming;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -185,6 +186,9 @@ public class RoomDetailActivity extends AppCompatActivity implements SurfaceHold
getSupportActionBar().hide();
}
// 设置沉浸式全屏模式
setupImmersiveMode();
android.util.Log.d("RoomDetail", "开始inflate布局");
binding = ActivityRoomDetailNewBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
@ -242,6 +246,28 @@ public class RoomDetailActivity extends AppCompatActivity implements SurfaceHold
}
}
/**
* 设置沉浸式全屏模式
*/
private void setupImmersiveMode() {
// 设置全屏沉浸式体验
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
);
// 设置状态栏透明
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(android.graphics.Color.TRANSPARENT);
}
// 隐藏导航栏可选
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
private void setupUI() {
// 返回按钮
binding.backButton.setOnClickListener(v -> finish());
@ -954,13 +980,15 @@ public class RoomDetailActivity extends AppCompatActivity implements SurfaceHold
// 横屏时隐藏其他UI元素只显示播放器
binding.topBar.setVisibility(View.GONE);
binding.roomInfoLayout.setVisibility(View.GONE);
binding.chatLayout.setVisibility(View.GONE);
binding.exitFullscreenButton.setVisibility(View.GONE);
binding.chatInputLayout.setVisibility(View.GONE);
binding.chatRecyclerView.setVisibility(View.GONE);
binding.exitFullscreenButton.setVisibility(View.VISIBLE);
} else {
// 竖屏时显示所有UI元素
binding.topBar.setVisibility(View.VISIBLE);
binding.roomInfoLayout.setVisibility(View.VISIBLE);
binding.chatLayout.setVisibility(View.VISIBLE);
binding.chatInputLayout.setVisibility(View.VISIBLE);
binding.chatRecyclerView.setVisibility(View.VISIBLE);
binding.exitFullscreenButton.setVisibility(View.GONE);
}
}

View File

@ -784,4 +784,102 @@ public interface ApiService {
*/
@GET("api/front/fan-group/all")
Call<ApiResponse<List<Map<String, Object>>>> getAllMyFanGroups();
// ==================== 用户活动记录 ====================
/**
* 获取观看历史
*/
@GET("api/front/activity/view-history")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getViewHistory(
@Query("type") String type,
@Query("page") int page,
@Query("pageSize") int pageSize);
/**
* 清除观看历史
*/
@DELETE("api/front/activity/view-history")
Call<ApiResponse<String>> clearViewHistory(@Query("type") String type);
/**
* 获取点赞记录
*/
@GET("api/front/activity/like-records")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getLikeRecords(
@Query("type") String type,
@Query("page") int page,
@Query("pageSize") int pageSize);
/**
* 获取收藏的作品
*/
@GET("api/front/activity/collected-works")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getCollectedWorks(
@Query("page") int page,
@Query("pageSize") int pageSize);
/**
* 获取关注记录
*/
@GET("api/front/activity/follow-records")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getFollowRecords(
@Query("page") int page,
@Query("pageSize") int pageSize);
/**
* 获取消费记录
*/
@GET("api/front/activity/consume-records")
Call<ApiResponse<List<Map<String, Object>>>> getConsumeRecords(
@Query("page") int page,
@Query("pageSize") int pageSize);
/**
* 记录观看历史新版
*/
@POST("api/front/activity/record-view")
Call<ApiResponse<Map<String, Object>>> recordViewHistoryNew(@Body Map<String, Object> body);
/**
* 调试Token
*/
@GET("api/front/activity/debug-token")
Call<ApiResponse<Map<String, Object>>> debugToken();
// ==================== 黑名单接口 ====================
/**
* 获取我的黑名单
*/
@GET("api/front/blacklist/list")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getMyBlacklist(
@Query("page") int page,
@Query("pageSize") int pageSize);
/**
* 添加到黑名单
*/
@POST("api/front/blacklist/add")
Call<ApiResponse<String>> addToBlacklist(@Body Map<String, Object> body);
/**
* 从黑名单移除
*/
@POST("api/front/blacklist/remove")
Call<ApiResponse<Map<String, Object>>> removeFromBlacklist(@Body Map<String, Object> body);
/**
* 检查是否在黑名单中
*/
@GET("api/front/blacklist/check/{userId}")
Call<ApiResponse<Map<String, Object>>> checkBlacklist(@Path("userId") int userId);
// ==================== 直播分类 ====================
/**
* 获取直播分类列表
*/
@GET("api/front/live/types")
Call<ApiResponse<List<LiveTypeResponse>>> getLiveTypes();
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#40000000" />
<corners android:radius="20dp" />
<stroke
android:width="1dp"
android:color="#40FFFFFF" />
</shape>

View File

@ -5,15 +5,17 @@
android:layout_height="match_parent"
android:background="#000000">
<!-- 使用 SurfaceView 作为摄像头预览,支持 RootEncoder RTMP 推流 -->
<SurfaceView
android:id="@+id/surfaceView"
<!-- 使用 OpenGlView 作为摄像头预览,支持 RootEncoder RTMP 推流和视频编码 -->
<com.pedro.rtplibrary.view.OpenGlView
android:id="@+id/openGlView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:keepAspectRatio="true"
app:aspectRatioMode="adjust" />
<!-- 顶部工具栏 -->
<LinearLayout

View File

@ -1,149 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout 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:background="#000000"
android:fitsSystemWindows="true">
android:background="#000000">
<!-- 顶部标题栏 -->
<LinearLayout
android:id="@+id/topBar"
android:layout_width="0dp"
android:layout_height="48dp"
android:background="#1A1A1A"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/backButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="返回"
android:src="@drawable/ic_arrow_back_24"
app:tint="@android:color/white" />
<TextView
android:id="@+id/topTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:text="直播间"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
<!-- 直播状态标签 -->
<TextView
android:id="@+id/liveTag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="@drawable/live_badge_background"
android:paddingHorizontal="8dp"
android:paddingVertical="3dp"
android:text="● 直播中"
android:textColor="@android:color/white"
android:textSize="11sp"
android:visibility="gone" />
<!-- 观看人数 -->
<LinearLayout
android:id="@+id/topViewerLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginEnd="4dp"
android:src="@drawable/ic_people_24"
app:tint="#AAAAAA" />
<TextView
android:id="@+id/topViewerCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textColor="#AAAAAA"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<!-- 播放器容器 -->
<!-- 全屏播放器容器 - 底层,视频填充整个屏幕 -->
<FrameLayout
android:id="@+id/playerContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#000000"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/topBar">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<SurfaceView
android:id="@+id/flvSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:visibility="gone" />
<TextureView
android:id="@+id/flvTextureView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:visibility="gone" />
<!-- ExoPlayer 播放器 - 填充模式 -->
<androidx.media3.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
app:resize_mode="fill"
app:use_controller="false"
app:show_buffering="when_playing" />
app:show_buffering="when_playing"
app:surface_type="texture_view" />
<!-- GSYVideoPlayer 播放器视图 -->
<!-- GSYVideoPlayer 播放器视图 - 填充模式 -->
<com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
android:id="@+id/gsyPlayer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:visibility="gone" />
<ImageButton
android:id="@+id/exitFullscreenButton"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_gravity="top|start"
android:layout_margin="12dp"
android:background="@drawable/bg_circle_white_60"
android:contentDescription="退出全屏"
android:src="@drawable/ic_arrow_back_24"
android:tint="@android:color/white"
android:visibility="gone" />
<!-- 横屏按钮 -->
<ImageButton
android:id="@+id/fullscreenButton"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_gravity="bottom|end"
android:layout_margin="12dp"
android:background="@drawable/bg_circle_white_60"
android:contentDescription="全屏"
android:src="@drawable/ic_fullscreen_24"
android:tint="@android:color/white" />
<!-- 离线提示 -->
<LinearLayout
android:id="@+id/offlineLayout"
@ -180,82 +81,328 @@
</FrameLayout>
<!-- 直播信息区域 -->
<!-- UI覆盖层 - 悬浮在视频上方,透明背景 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent">
<!-- 顶部渐变遮罩 - 半透明 -->
<View
android:id="@+id/topGradient"
android:layout_width="0dp"
android:layout_height="100dp"
android:background="@drawable/bg_gradient_top"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 顶部工具栏 - 悬浮 -->
<LinearLayout
android:id="@+id/roomInfoLayout"
android:id="@+id/topBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="12dp"
android:paddingVertical="10dp"
android:paddingTop="36dp"
android:paddingBottom="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/playerContainer">
app:layout_constraintTop_toTopOf="parent">
<ImageView
<ImageButton
android:id="@+id/backButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="10dp"
android:background="@drawable/bg_avatar_circle"
android:src="@drawable/ic_person_24"
android:tint="@android:color/white" />
android:background="@drawable/bg_circle_semi_transparent"
android:contentDescription="返回"
android:src="@drawable/ic_arrow_back_24"
app:tint="@android:color/white" />
<LinearLayout
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<!-- 直播状态标签 -->
<TextView
android:id="@+id/liveTag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
android:layout_marginEnd="8dp"
android:background="@drawable/live_badge_background"
android:paddingHorizontal="8dp"
android:paddingVertical="3dp"
android:text="● 直播中"
android:textColor="@android:color/white"
android:textSize="11sp"
android:visibility="visible" />
<!-- 观看人数 -->
<LinearLayout
android:id="@+id/topViewerLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_semi_transparent"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginEnd="4dp"
android:src="@drawable/ic_people_24"
app:tint="@android:color/white" />
<TextView
android:id="@+id/roomTitle"
android:layout_width="match_parent"
android:id="@+id/topViewerCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="直播间标题"
android:textColor="#333333"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/streamerName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="主播名称"
android:textColor="#999999"
android:text="0"
android:textColor="@android:color/white"
android:textSize="12sp" />
</LinearLayout>
<ImageButton
android:id="@+id/shareButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="分享"
android:src="@drawable/ic_share_24"
android:tint="#666666" />
</LinearLayout>
<!-- 隐藏的标题(用于代码引用) -->
<TextView
android:id="@+id/topTitle"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 主播信息卡片 - 悬浮在左上角 -->
<LinearLayout
android:id="@+id/roomInfoLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:background="@drawable/bg_rounded_semi_transparent"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="8dp"
android:paddingVertical="6dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/topBar">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/streamerAvatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_person_24"
app:civ_border_color="#FFFFFF"
app:civ_border_width="1dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/streamerName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主播"
android:textColor="@android:color/white"
android:textSize="13sp"
android:textStyle="bold" />
<TextView
android:id="@+id/roomTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="100dp"
android:ellipsize="end"
android:maxLines="1"
android:text="直播间"
android:textColor="#CCFFFFFF"
android:textSize="11sp" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/followButton"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="92dp"
android:layout_height="40dp"
android:paddingHorizontal="14dp"
android:layout_width="wrap_content"
android:layout_height="26dp"
android:layout_marginStart="8dp"
android:minWidth="48dp"
android:paddingHorizontal="10dp"
android:text="关注"
android:textColor="@android:color/white"
android:textSize="12sp"
android:maxLines="1"
android:ellipsize="end"
android:textSize="11sp"
android:textAllCaps="false"
app:backgroundTint="#FF2FD3C7"
app:icon="@drawable/ic_heart_24"
app:iconSize="14dp"
app:iconTint="@android:color/white" />
app:backgroundTint="#FF4081"
app:cornerRadius="13dp" />
</LinearLayout>
<!-- 底部渐变遮罩 -->
<View
android:id="@+id/bottomGradient"
android:layout_width="0dp"
android:layout_height="180dp"
android:background="@drawable/bg_gradient_bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- 弹幕列表 - 左下角悬浮 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/chatRecyclerView"
android:layout_width="0dp"
android:layout_height="160dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="70dp"
android:layout_marginBottom="4dp"
android:clipToPadding="false"
android:paddingTop="8dp"
android:background="@android:color/transparent"
app:layout_constraintBottom_toTopOf="@id/chatInputLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- 右侧悬浮功能按钮 -->
<LinearLayout
android:id="@+id/rightButtons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginBottom="70dp"
android:gravity="center_horizontal"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@id/chatInputLayout"
app:layout_constraintEnd_toEndOf="parent">
<!-- 点赞按钮 -->
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
<ImageButton
android:id="@+id/likeButton"
android:layout_width="44dp"
android:layout_height="44dp"
android:background="@drawable/bg_circle_semi_transparent"
android:contentDescription="点赞"
android:src="@drawable/ic_like_24"
android:tint="#FF4081" />
<TextView
android:id="@+id/likeCountText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="-2dp"
android:background="@drawable/bg_purple_20"
android:paddingHorizontal="5dp"
android:paddingVertical="1dp"
android:text="0"
android:textColor="#FFFFFF"
android:textSize="9sp" />
</FrameLayout>
<!-- 礼物按钮 -->
<ImageButton
android:id="@+id/giftButton"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginBottom="12dp"
android:background="@drawable/bg_circle_semi_transparent"
android:contentDescription="送礼物"
android:src="@drawable/ic_gift_24"
android:tint="#FF9800" />
<!-- 分享按钮 -->
<ImageButton
android:id="@+id/shareButton"
android:layout_width="44dp"
android:layout_height="44dp"
android:background="@drawable/bg_circle_semi_transparent"
android:contentDescription="分享"
android:src="@drawable/ic_share_24"
android:tint="@android:color/white" />
</LinearLayout>
<!-- 全屏按钮 - 右下角 -->
<ImageButton
android:id="@+id/fullscreenButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_circle_semi_transparent"
android:contentDescription="全屏"
android:src="@drawable/ic_fullscreen_24"
android:tint="@android:color/white"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/rightButtons"
app:layout_constraintEnd_toEndOf="parent" />
<!-- 退出全屏按钮 -->
<ImageButton
android:id="@+id/exitFullscreenButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_margin="12dp"
android:visibility="gone"
android:background="@drawable/bg_circle_semi_transparent"
android:contentDescription="退出全屏"
android:src="@drawable/ic_arrow_back_24"
android:tint="@android:color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 底部输入栏 - 悬浮 -->
<LinearLayout
android:id="@+id/chatInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="12dp"
android:paddingVertical="10dp"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/chatInput"
android:layout_width="0dp"
android:layout_height="38dp"
android:layout_weight="1"
android:background="@drawable/bg_chat_input_dark"
android:hint="说点什么..."
android:textColorHint="#80FFFFFF"
android:imeOptions="actionSend"
android:inputType="text"
android:maxLines="1"
android:paddingHorizontal="14dp"
android:textColor="@android:color/white"
android:textSize="13sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sendButton"
android:layout_width="wrap_content"
android:layout_height="38dp"
android:layout_marginStart="8dp"
android:text="发送"
android:textSize="12sp"
app:backgroundTint="#FF4081"
app:cornerRadius="19dp" />
</LinearLayout>
@ -270,110 +417,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomInfoLayout" />
<!-- 弹幕区域 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/chatLayout"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/fanGroupBannerInclude">
<!-- 弹幕列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/chatRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
app:layout_constraintBottom_toTopOf="@id/chatInputLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 弹幕输入区域 -->
<LinearLayout
android:id="@+id/chatInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#F5F5F5"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/chatInput"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:background="@drawable/bg_white_16"
android:hint="说点什么..."
android:imeOptions="actionSend"
android:inputType="text"
android:maxLines="1"
android:paddingHorizontal="12dp"
android:textSize="14sp" />
<!-- 点赞按钮容器 -->
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginStart="8dp">
<ImageButton
android:id="@+id/likeButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="点赞"
android:src="@drawable/ic_like_24"
android:tint="#FF4081" />
<TextView
android:id="@+id/likeCountText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="-4dp"
android:background="@drawable/bg_purple_20"
android:paddingHorizontal="4dp"
android:paddingVertical="1dp"
android:text="0"
android:textColor="#FFFFFF"
android:textSize="9sp"
android:visibility="visible" />
</FrameLayout>
<!-- 礼物按钮 -->
<ImageButton
android:id="@+id/giftButton"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="送礼物"
android:src="@drawable/ic_gift_24"
android:tint="#FF9800" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sendButton"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:text="发送"
android:textSize="12sp"
app:backgroundTint="@color/purple_500" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- 加载指示器 -->
@ -381,20 +424,15 @@
android:id="@+id/loadingIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_gravity="center"
android:visibility="gone" />
<!-- 礼物特效覆盖层 - 显示在发送按钮上方 -->
<!-- 礼物特效覆盖层 -->
<include
android:id="@+id/giftAnimationOverlay"
layout="@layout/gift_animation_overlay"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
android:layout_gravity="bottom" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=file:///D:/soft/gradle-8.1-bin.zip
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14-bin.zip
networkTimeout=600000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists