添加直播按钮

This commit is contained in:
xiao12feng@outlook.com 2025-12-17 17:23:54 +08:00
parent ed9d9ceb9c
commit 0289dafd1c
19 changed files with 165 additions and 41 deletions

View File

@ -17,14 +17,27 @@ android {
} }
buildTypes { buildTypes {
release { debug {
// 调试版本优化
isMinifyEnabled = false isMinifyEnabled = false
isDebuggable = true
}
release {
// 发布版本优化
isMinifyEnabled = true
isShrinkResources = true
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" "proguard-rules.pro"
) )
} }
} }
// 编译优化
dexOptions {
javaMaxHeapSize = "2g"
preDexLibraries = true
}
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17

View File

@ -9,7 +9,9 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.LiveStreaming" android:theme="@style/Theme.LiveStreaming"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true"
android:hardwareAccelerated="true"
android:largeHeap="true">
<activity <activity
android:name="com.example.livestreaming.FishPondActivity" android:name="com.example.livestreaming.FishPondActivity"
@ -37,7 +39,9 @@
<activity <activity
android:name="com.example.livestreaming.MainActivity" android:name="com.example.livestreaming.MainActivity"
android:exported="true"> android:exported="true"
android:launchMode="singleTop"
android:screenOrientation="portrait">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@ -53,6 +53,15 @@ public class MainActivity extends AppCompatActivity {
binding = ActivityMainBinding.inflate(getLayoutInflater()); binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
// 立即显示缓存数据提升启动速度
setupRecyclerView();
setupUI();
// 异步加载资源文件避免阻塞主线程
loadCoverAssetsAsync();
}
private void setupRecyclerView() {
adapter = new RoomsAdapter(room -> { adapter = new RoomsAdapter(room -> {
if (room == null) return; if (room == null) return;
Intent intent = new Intent(MainActivity.this, RoomDetailActivity.class); Intent intent = new Intent(MainActivity.this, RoomDetailActivity.class);
@ -64,9 +73,12 @@ public class MainActivity extends AppCompatActivity {
glm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS); glm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
binding.roomsRecyclerView.setLayoutManager(glm); binding.roomsRecyclerView.setLayoutManager(glm);
binding.roomsRecyclerView.setAdapter(adapter); binding.roomsRecyclerView.setAdapter(adapter);
// 立即显示演示数据提升用户体验
adapter.submitList(buildDemoRooms(6));
}
loadCoverAssets(); private void setupUI() {
binding.swipeRefresh.setOnRefreshListener(this::fetchRooms); binding.swipeRefresh.setOnRefreshListener(this::fetchRooms);
binding.roomsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { binding.roomsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@ -95,6 +107,9 @@ public class MainActivity extends AppCompatActivity {
} }
}); });
// 设置添加直播按钮点击事件
binding.fabAddLive.setOnClickListener(v -> showCreateRoomDialog());
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation; BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
bottomNavigation.setSelectedItemId(R.id.nav_home); bottomNavigation.setSelectedItemId(R.id.nav_home);
bottomNavigation.setOnItemSelectedListener(item -> { bottomNavigation.setOnItemSelectedListener(item -> {
@ -151,9 +166,11 @@ public class MainActivity extends AppCompatActivity {
stopPolling(); stopPolling();
pollRunnable = () -> { pollRunnable = () -> {
fetchRooms(); fetchRooms();
handler.postDelayed(pollRunnable, 5000); // 增加轮询间隔减少网络请求频率
handler.postDelayed(pollRunnable, 10000);
}; };
handler.post(pollRunnable); // 延迟首次请求让界面先显示
handler.postDelayed(pollRunnable, 1000);
} }
private void stopPolling() { private void stopPolling() {
@ -210,9 +227,13 @@ public class MainActivity extends AppCompatActivity {
dialog.dismiss(); dialog.dismiss();
fetchRooms(); fetchRooms();
Intent intent = new Intent(MainActivity.this, RoomDetailActivity.class); // 显示推流信息
intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId()); showStreamInfo(room);
startActivity(intent);
// 可选跳转到房间详情
// Intent intent = new Intent(MainActivity.this, RoomDetailActivity.class);
// intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
// startActivity(intent);
} }
@Override @Override
@ -321,22 +342,31 @@ public class MainActivity extends AppCompatActivity {
return list; return list;
} }
private void loadCoverAssets() { private void loadCoverAssetsAsync() {
AssetManager am = getAssets(); // 在后台线程加载资源文件避免阻塞UI
List<String> files = new ArrayList<>(); new Thread(() -> {
try { AssetManager am = getAssets();
String[] list = am.list("img"); List<String> files = new ArrayList<>();
if (list != null) { try {
for (String f : list) { String[] list = am.list("img");
if (f == null) continue; if (list != null) {
String lower = f.toLowerCase(); for (String f : list) {
if (lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".webp") || lower.endsWith(".gif")) { if (f == null) continue;
files.add(f); String lower = f.toLowerCase();
if (lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".webp") || lower.endsWith(".gif")) {
files.add(f);
}
} }
} }
} catch (IOException ignored) {
} }
} catch (IOException ignored) {
} // 回到主线程更新UI
adapter.setCoverAssetFiles(files); runOnUiThread(() -> {
if (adapter != null) {
adapter.setCoverAssetFiles(files);
}
});
}).start();
} }
} }

View File

@ -38,8 +38,25 @@ public class PlayerActivity extends AppCompatActivity {
triedAltUrl = false; triedAltUrl = false;
ExoPlayer exo = new ExoPlayer.Builder(this).build(); // 创建低延迟播放器配置
ExoPlayer exo = new ExoPlayer.Builder(this)
.setLoadControl(new androidx.media3.exoplayer.DefaultLoadControl.Builder()
// 减少缓冲区大小降低延迟
.setBufferDurationsMs(
1000, // 最小缓冲时长 1秒
3000, // 最大缓冲时长 3秒
500, // 播放缓冲时长 0.5秒
1000 // 播放后缓冲时长 1秒
)
.build())
.build();
// 设置播放器视图
binding.playerView.setPlayer(exo); binding.playerView.setPlayer(exo);
// 启用低延迟模式
binding.playerView.setUseController(true);
binding.playerView.setControllerAutoShow(false);
String altUrl = getAltHlsUrl(url); String altUrl = getAltHlsUrl(url);
exo.addListener(new Player.Listener() { exo.addListener(new Player.Listener() {

View File

@ -25,6 +25,9 @@ public final class ApiClient {
OkHttpClient client = new OkHttpClient.Builder() OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging) .addInterceptor(logging)
.connectTimeout(5, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
.writeTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
.build(); .build();
retrofit = new Retrofit.Builder() retrofit = new Retrofit.Builder()

View File

@ -21,6 +21,9 @@ public class Room {
@SerializedName("isLive") @SerializedName("isLive")
private boolean isLive; private boolean isLive;
@SerializedName("viewerCount")
private int viewerCount;
@SerializedName("streamUrls") @SerializedName("streamUrls")
private StreamUrls streamUrls; private StreamUrls streamUrls;
@ -58,6 +61,10 @@ public class Room {
this.streamUrls = streamUrls; this.streamUrls = streamUrls;
} }
public void setViewerCount(int viewerCount) {
this.viewerCount = viewerCount;
}
public String getId() { public String getId() {
return id; return id;
} }
@ -82,12 +89,17 @@ public class Room {
return streamUrls; return streamUrls;
} }
public int getViewerCount() {
return viewerCount;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (!(o instanceof Room)) return false; if (!(o instanceof Room)) return false;
Room room = (Room) o; Room room = (Room) o;
return isLive == room.isLive return isLive == room.isLive
&& viewerCount == room.viewerCount
&& Objects.equals(id, room.id) && Objects.equals(id, room.id)
&& Objects.equals(title, room.title) && Objects.equals(title, room.title)
&& Objects.equals(streamerName, room.streamerName) && Objects.equals(streamerName, room.streamerName)
@ -97,6 +109,6 @@ public class Room {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, title, streamerName, streamKey, isLive, streamUrls); return Objects.hash(id, title, streamerName, streamKey, isLive, viewerCount, streamUrls);
} }
} }

View File

@ -212,6 +212,17 @@
android:visibility="gone" android:visibility="gone"
/> />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAddLive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="88dp"
android:contentDescription="添加直播"
android:src="@drawable/ic_add_24"
app:backgroundTint="@color/purple_500"
app:tint="@android:color/white" />
<include <include
android:id="@+id/bottomNavInclude" android:id="@+id/bottomNavInclude"
layout="@layout/include_bottom_nav" layout="@layout/include_bottom_nav"

View File

@ -1,48 +1,82 @@
# SRS 配置文件 - 直播系统
listen 1935; listen 1935;
max_connections 1000; max_connections 1000;
daemon off; daemon off;
srs_log_tank console; srs_log_tank console;
# 全局优化配置
# 减少缓冲区大小,降低延迟
mr_enabled off;
# 启用快速启动
fast_cache 10;
# 优化TCP配置
tcp_nodelay on;
http_server { http_server {
enabled on; enabled on;
listen 8080; listen 8080;
dir ./objs/nginx/html; dir ./objs/nginx/html;
# 启用跨域支持
crossdomain on; crossdomain on;
} }
http_api { http_api {
enabled on; enabled on;
listen 1985; listen 1985;
# 启用跨域支持
crossdomain on;
}
stats {
network 0;
} }
vhost __defaultVhost__ { vhost __defaultVhost__ {
# HLS 配置 # RTMP 配置 - 优化延迟
rtmp {
enabled on;
# 减少缓冲区大小,降低延迟
chunk_size 4096;
}
# HLS 配置 - 优化延迟
hls { hls {
enabled on; enabled on;
hls_fragment 2;
hls_window 10;
hls_path ./objs/nginx/html; hls_path ./objs/nginx/html;
hls_m3u8_file [app]/[stream].m3u8; # 减少分片时长,降低延迟
hls_ts_file [app]/[stream]-[seq].ts; hls_fragment 2;
hls_window 6;
# 启用低延迟模式
hls_dispose 30;
} }
# HTTP-FLV 配置 (低延迟) # HTTP-FLV 配置 - 低延迟播放
http_remux { http_remux {
enabled on; enabled on;
mount [app]/[stream].flv; mount [vhost]/[app]/[stream].flv;
# 启用快速启动
fast_cache 10;
} }
# HTTP 回调配置 # 转码配置(可选)
http_hooks { transcode {
enabled on; enabled off;
on_publish http://host.docker.internal:3001/api/srs/on_publish;
on_unpublish http://host.docker.internal:3001/api/srs/on_unpublish;
} }
# GOP 缓存,提高首屏速度 # 播放配置 - 优化延迟
play { play {
gop_cache on; # 减少GOP缓存
gop_cache off;
# 启用时间校正
time_jitter full;
# 减少队列长度
queue_length 10; queue_length 10;
} }
# 发布配置 - 优化延迟
publish {
# 减少首帧等待时间
firstpkt_timeout 20000;
# 减少正常包超时
normal_timeout 5000;
}
} }