zhibo/android-app/app/src/main/java/com/example/livestreaming/PlayerActivity.java

234 lines
8.1 KiB
Java
Raw Normal View History

2025-12-16 16:00:11 +08:00
package com.example.livestreaming;
import android.os.Bundle;
import android.view.Surface;
import android.view.TextureView;
2025-12-16 16:00:11 +08:00
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.MediaItem;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.exoplayer.ExoPlayer;
import com.example.livestreaming.databinding.ActivityPlayerBinding;
import tv.danmaku.ijk.media.player.IMediaPlayer;
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
2025-12-16 16:00:11 +08:00
public class PlayerActivity extends AppCompatActivity {
public static final String EXTRA_PLAY_URL = "extra_play_url";
public static final String EXTRA_TITLE = "extra_title";
private ActivityPlayerBinding binding;
private ExoPlayer player;
private boolean triedAltUrl = false;
private IjkMediaPlayer ijkPlayer;
private Surface ijkSurface;
private String ijkUrl;
private String ijkFallbackHlsUrl;
private boolean ijkFallbackTried;
private static boolean ijkLibLoaded;
2025-12-16 16:00:11 +08:00
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityPlayerBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
String t = getIntent().getStringExtra(EXTRA_TITLE);
setTitle(t != null ? t : "Live");
}
@Override
protected void onStart() {
super.onStart();
2025-12-23 18:09:56 +08:00
// TODO: 接入后端接口 - 记录播放开始
// 接口路径: POST /api/play/start
// 请求参数:
// - userId: 当前用户ID从token中获取可选
// - roomId: 房间ID从Intent中获取
// - playUrl: 播放地址
// - timestamp: 开始播放时间戳
// 返回数据格式: ApiResponse<{success: boolean}>
// 用于统计播放数据和用户行为分析
2025-12-16 16:00:11 +08:00
String url = getIntent().getStringExtra(EXTRA_PLAY_URL);
if (url == null || url.trim().isEmpty()) return;
triedAltUrl = false;
if (url.endsWith(".flv")) {
startFlv(url, null);
return;
}
startHls(url, null);
}
private void startHls(String url, @Nullable String altUrl) {
releaseIjkPlayer();
if (binding != null) {
binding.flvTextureView.setVisibility(android.view.View.GONE);
binding.playerView.setVisibility(android.view.View.VISIBLE);
}
releaseExoPlayer();
triedAltUrl = false;
// 优化缓冲配置,平衡延迟和流畅度
androidx.media3.exoplayer.DefaultLoadControl loadControl =
new androidx.media3.exoplayer.DefaultLoadControl.Builder()
.setBufferDurationsMs(
3000, // 最小缓冲 3秒
15000, // 最大缓冲 15秒
1500, // 播放前缓冲 1.5秒
3000 // 重新缓冲 3秒
)
.setPrioritizeTimeOverSizeThresholds(true)
.build();
2025-12-17 17:23:54 +08:00
ExoPlayer exo = new ExoPlayer.Builder(this)
.setLoadControl(loadControl)
2025-12-17 17:23:54 +08:00
.build();
2025-12-16 16:00:11 +08:00
binding.playerView.setPlayer(exo);
2025-12-17 17:23:54 +08:00
binding.playerView.setUseController(true);
binding.playerView.setControllerAutoShow(false);
2025-12-16 16:00:11 +08:00
String computedAltUrl = altUrl;
if (computedAltUrl == null || computedAltUrl.trim().isEmpty()) {
computedAltUrl = getAltHlsUrl(url);
}
String finalComputedAltUrl = computedAltUrl;
2025-12-16 16:00:11 +08:00
exo.addListener(new Player.Listener() {
@Override
public void onPlayerError(PlaybackException error) {
android.util.Log.e("PlayerActivity", "播放错误: " + error.getMessage());
2025-12-16 16:00:11 +08:00
if (triedAltUrl) return;
if (finalComputedAltUrl == null || finalComputedAltUrl.trim().isEmpty()) return;
2025-12-16 16:00:11 +08:00
triedAltUrl = true;
exo.setMediaItem(MediaItem.fromUri(finalComputedAltUrl));
2025-12-16 16:00:11 +08:00
exo.prepare();
exo.setPlayWhenReady(true);
}
});
android.util.Log.d("PlayerActivity", "开始播放: " + url);
2025-12-16 16:00:11 +08:00
exo.setMediaItem(MediaItem.fromUri(url));
exo.prepare();
exo.setPlayWhenReady(true);
player = exo;
}
@Override
protected void onStop() {
super.onStop();
2025-12-23 18:09:56 +08:00
// TODO: 接入后端接口 - 记录播放结束
// 接口路径: POST /api/play/end
// 请求参数:
// - userId: 当前用户ID从token中获取可选
// - roomId: 房间ID从Intent中获取
// - playDuration: 播放时长(秒)
// - timestamp: 结束播放时间戳
// 返回数据格式: ApiResponse<{success: boolean}>
// 用于统计播放时长和用户观看行为
releaseExoPlayer();
releaseIjkPlayer();
}
private void startFlv(String flvUrl, @Nullable String fallbackHlsUrl) {
// 禁用 IjkPlayer直接使用 HLS 播放IjkPlayer 在某些设备上会崩溃)
android.util.Log.d("PlayerActivity", "FLV流转为HLS播放: " + flvUrl);
// 将 FLV 地址转换为 HLS 地址
String hlsUrl = fallbackHlsUrl;
if (hlsUrl == null || hlsUrl.trim().isEmpty()) {
hlsUrl = flvUrl.replace(".flv", ".m3u8");
}
startHls(hlsUrl, null);
}
private void prepareIjk(String url) {
if (ijkSurface == null) return;
IjkMediaPlayer p = new IjkMediaPlayer();
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);
p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");
p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 1);
p.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 1024);
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 300);
p.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1);
p.setOnPreparedListener(mp -> mp.start());
p.setOnErrorListener((IMediaPlayer mp, int what, int extra) -> {
if (ijkFallbackTried || ijkFallbackHlsUrl == null || ijkFallbackHlsUrl.trim().isEmpty()) return true;
ijkFallbackTried = true;
startHls(ijkFallbackHlsUrl, null);
return true;
});
ijkPlayer = p;
try {
p.setSurface(ijkSurface);
p.setDataSource(url);
p.prepareAsync();
} catch (Exception e) {
if (ijkFallbackHlsUrl != null && !ijkFallbackHlsUrl.trim().isEmpty()) {
startHls(ijkFallbackHlsUrl, null);
}
}
}
private static void ensureIjkLibsLoaded() {
if (ijkLibLoaded) return;
try {
IjkMediaPlayer.loadLibrariesOnce(null);
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
} catch (Throwable ignored) {
}
ijkLibLoaded = true;
}
private void releaseExoPlayer() {
2025-12-16 16:00:11 +08:00
if (player != null) {
player.release();
player = null;
}
if (binding != null) binding.playerView.setPlayer(null);
}
private void releaseIjkPlayer() {
if (ijkPlayer != null) {
ijkPlayer.reset();
ijkPlayer.release();
ijkPlayer = null;
}
if (ijkSurface != null) {
ijkSurface.release();
ijkSurface = null;
}
ijkUrl = null;
ijkFallbackHlsUrl = null;
ijkFallbackTried = false;
if (binding != null) {
binding.flvTextureView.setVisibility(android.view.View.GONE);
binding.playerView.setVisibility(android.view.View.VISIBLE);
}
2025-12-16 16:00:11 +08:00
}
private String getAltHlsUrl(String url) {
if (!url.endsWith(".m3u8")) return null;
if (url.contains("/index.m3u8")) return null;
return url.substring(0, url.length() - ".m3u8".length()) + "/index.m3u8";
}
}