feat(android): add IjkPlayer flv playback with hls fallback
This commit is contained in:
parent
087e1feae6
commit
9cf2beef40
|
|
@ -470,20 +470,8 @@ public class MainActivity extends AppCompatActivity {
|
|||
String streamKey = room != null ? room.getStreamKey() : null;
|
||||
String rtmp = room != null && room.getStreamUrls() != null ? room.getStreamUrls().getRtmp() : null;
|
||||
|
||||
// 将RTMP地址中的10.0.2.2或192.168.x.x替换为localhost供OBS使用
|
||||
// 直接使用服务器返回的RTMP地址
|
||||
String rtmpForObs = rtmp;
|
||||
if (!TextUtils.isEmpty(rtmp)) {
|
||||
try {
|
||||
Uri u = Uri.parse(rtmp);
|
||||
int port = u.getPort();
|
||||
String path = u.getPath();
|
||||
if (port > 0 && !TextUtils.isEmpty(path)) {
|
||||
// OBS推流时使用localhost
|
||||
rtmpForObs = "rtmp://localhost:" + port + path;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建自定义弹窗布局
|
||||
View dialogView = getLayoutInflater().inflate(R.layout.dialog_stream_info, null);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.example.livestreaming;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
|
@ -11,6 +13,9 @@ 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;
|
||||
|
||||
public class PlayerActivity extends AppCompatActivity {
|
||||
|
||||
public static final String EXTRA_PLAY_URL = "extra_play_url";
|
||||
|
|
@ -20,6 +25,14 @@ public class PlayerActivity extends AppCompatActivity {
|
|||
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;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
@ -38,6 +51,24 @@ public class PlayerActivity extends AppCompatActivity {
|
|||
|
||||
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;
|
||||
|
||||
// 创建低延迟播放器配置
|
||||
ExoPlayer exo = new ExoPlayer.Builder(this)
|
||||
.setLoadControl(new androidx.media3.exoplayer.DefaultLoadControl.Builder()
|
||||
|
|
@ -58,15 +89,18 @@ public class PlayerActivity extends AppCompatActivity {
|
|||
binding.playerView.setUseController(true);
|
||||
binding.playerView.setControllerAutoShow(false);
|
||||
|
||||
String altUrl = getAltHlsUrl(url);
|
||||
String computedAltUrl = altUrl;
|
||||
if (computedAltUrl == null || computedAltUrl.trim().isEmpty()) computedAltUrl = getAltHlsUrl(url);
|
||||
|
||||
String finalComputedAltUrl = computedAltUrl;
|
||||
exo.addListener(new Player.Listener() {
|
||||
@Override
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
if (triedAltUrl) return;
|
||||
if (altUrl == null || altUrl.trim().isEmpty()) return;
|
||||
if (finalComputedAltUrl == null || finalComputedAltUrl.trim().isEmpty()) return;
|
||||
triedAltUrl = true;
|
||||
|
||||
exo.setMediaItem(MediaItem.fromUri(altUrl));
|
||||
exo.setMediaItem(MediaItem.fromUri(finalComputedAltUrl));
|
||||
exo.prepare();
|
||||
exo.setPlayWhenReady(true);
|
||||
}
|
||||
|
|
@ -81,10 +115,122 @@ public class PlayerActivity extends AppCompatActivity {
|
|||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
releaseExoPlayer();
|
||||
releaseIjkPlayer();
|
||||
}
|
||||
|
||||
private void startFlv(String flvUrl, @Nullable String fallbackHlsUrl) {
|
||||
ensureIjkLibsLoaded();
|
||||
releaseExoPlayer();
|
||||
releaseIjkPlayer();
|
||||
|
||||
ijkUrl = flvUrl;
|
||||
ijkFallbackHlsUrl = fallbackHlsUrl;
|
||||
ijkFallbackTried = false;
|
||||
|
||||
if (binding != null) {
|
||||
binding.playerView.setVisibility(android.view.View.GONE);
|
||||
binding.flvTextureView.setVisibility(android.view.View.VISIBLE);
|
||||
}
|
||||
|
||||
TextureView view = binding.flvTextureView;
|
||||
TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(android.graphics.SurfaceTexture surfaceTexture, int width, int height) {
|
||||
ijkSurface = new Surface(surfaceTexture);
|
||||
prepareIjk(flvUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(android.graphics.SurfaceTexture surface, int width, int height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(android.graphics.SurfaceTexture surface) {
|
||||
releaseIjkPlayer();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(android.graphics.SurfaceTexture surface) {
|
||||
}
|
||||
};
|
||||
|
||||
view.setSurfaceTextureListener(listener);
|
||||
if (view.isAvailable() && view.getSurfaceTexture() != null) {
|
||||
ijkSurface = new Surface(view.getSurfaceTexture());
|
||||
prepareIjk(flvUrl);
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAltHlsUrl(String url) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import android.os.Bundle;
|
|||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
|
@ -26,6 +28,9 @@ import com.example.livestreaming.net.ApiClient;
|
|||
import com.example.livestreaming.net.ApiResponse;
|
||||
import com.example.livestreaming.net.Room;
|
||||
|
||||
import tv.danmaku.ijk.media.player.IMediaPlayer;
|
||||
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
|
@ -48,6 +53,13 @@ public class RoomDetailActivity extends AppCompatActivity {
|
|||
|
||||
private ExoPlayer player;
|
||||
private boolean triedAltUrl;
|
||||
private IjkMediaPlayer ijkPlayer;
|
||||
private Surface ijkSurface;
|
||||
private String ijkUrl;
|
||||
private String ijkFallbackHlsUrl;
|
||||
private boolean ijkFallbackTried;
|
||||
|
||||
private static boolean ijkLibLoaded;
|
||||
private boolean isFullscreen = false;
|
||||
|
||||
private ChatAdapter chatAdapter;
|
||||
|
|
@ -358,16 +370,16 @@ public class RoomDetailActivity extends AppCompatActivity {
|
|||
|
||||
// 获取播放地址
|
||||
String playUrl = null;
|
||||
String fallbackHlsUrl = null;
|
||||
if (r.getStreamUrls() != null) {
|
||||
// 优先使用HTTP-FLV,延迟更低
|
||||
playUrl = r.getStreamUrls().getFlv();
|
||||
if (TextUtils.isEmpty(playUrl)) {
|
||||
playUrl = r.getStreamUrls().getHls();
|
||||
}
|
||||
fallbackHlsUrl = r.getStreamUrls().getHls();
|
||||
if (TextUtils.isEmpty(playUrl)) playUrl = fallbackHlsUrl;
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(playUrl)) {
|
||||
ensurePlayer(playUrl);
|
||||
ensurePlayer(playUrl, fallbackHlsUrl);
|
||||
} else {
|
||||
// 没有播放地址时显示离线状态
|
||||
binding.offlineLayout.setVisibility(View.VISIBLE);
|
||||
|
|
@ -375,7 +387,15 @@ public class RoomDetailActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
private void ensurePlayer(String url) {
|
||||
private void ensurePlayer(String url, String fallbackHlsUrl) {
|
||||
if (TextUtils.isEmpty(url)) return;
|
||||
|
||||
if (url.endsWith(".flv")) {
|
||||
if (TextUtils.equals(ijkUrl, url) && ijkPlayer != null) return;
|
||||
startFlv(url, fallbackHlsUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (player != null) {
|
||||
MediaItem current = player.getCurrentMediaItem();
|
||||
String currentUri = current != null && current.localConfiguration != null && current.localConfiguration.uri != null
|
||||
|
|
@ -385,36 +405,45 @@ public class RoomDetailActivity extends AppCompatActivity {
|
|||
if (currentUri != null && currentUri.equals(url)) return;
|
||||
}
|
||||
|
||||
releasePlayer();
|
||||
startHls(url, null);
|
||||
}
|
||||
|
||||
private void startHls(String url, @Nullable String altUrl) {
|
||||
releaseIjkPlayer();
|
||||
if (binding != null) {
|
||||
binding.flvTextureView.setVisibility(View.GONE);
|
||||
binding.playerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
releaseExoPlayer();
|
||||
triedAltUrl = false;
|
||||
|
||||
// 创建低延迟播放器配置
|
||||
ExoPlayer exo = new ExoPlayer.Builder(this)
|
||||
.setLoadControl(new androidx.media3.exoplayer.DefaultLoadControl.Builder()
|
||||
// 减少缓冲区大小,降低延迟
|
||||
.setBufferDurationsMs(
|
||||
1000, // 最小缓冲时长 1秒
|
||||
3000, // 最大缓冲时长 3秒
|
||||
500, // 播放缓冲时长 0.5秒
|
||||
1000 // 播放后缓冲时长 1秒
|
||||
1000,
|
||||
3000,
|
||||
500,
|
||||
1000
|
||||
)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
binding.playerView.setPlayer(exo);
|
||||
|
||||
// 设置播放器监听器
|
||||
String altUrl = getAltHlsUrl(url);
|
||||
String computedAltUrl = altUrl;
|
||||
if (TextUtils.isEmpty(computedAltUrl)) computedAltUrl = getAltHlsUrl(url);
|
||||
|
||||
String finalComputedAltUrl = computedAltUrl;
|
||||
exo.addListener(new Player.Listener() {
|
||||
@Override
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
if (triedAltUrl || TextUtils.isEmpty(altUrl)) {
|
||||
// 播放失败,显示离线状态
|
||||
if (triedAltUrl || TextUtils.isEmpty(finalComputedAltUrl)) {
|
||||
binding.offlineLayout.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
triedAltUrl = true;
|
||||
exo.setMediaItem(MediaItem.fromUri(altUrl));
|
||||
exo.setMediaItem(MediaItem.fromUri(finalComputedAltUrl));
|
||||
exo.prepare();
|
||||
exo.setPlayWhenReady(true);
|
||||
}
|
||||
|
|
@ -422,10 +451,7 @@ public class RoomDetailActivity extends AppCompatActivity {
|
|||
@Override
|
||||
public void onPlaybackStateChanged(int playbackState) {
|
||||
if (playbackState == Player.STATE_READY) {
|
||||
// 播放成功,隐藏离线状态
|
||||
binding.offlineLayout.setVisibility(View.GONE);
|
||||
|
||||
// 添加系统消息
|
||||
addChatMessage(new ChatMessage("直播已连接,开始观看吧!", true));
|
||||
}
|
||||
}
|
||||
|
|
@ -437,12 +463,133 @@ public class RoomDetailActivity extends AppCompatActivity {
|
|||
player = exo;
|
||||
}
|
||||
|
||||
private void startFlv(String flvUrl, @Nullable String fallbackHlsUrl) {
|
||||
ensureIjkLibsLoaded();
|
||||
releaseExoPlayer();
|
||||
releaseIjkPlayer();
|
||||
|
||||
ijkUrl = flvUrl;
|
||||
ijkFallbackHlsUrl = fallbackHlsUrl;
|
||||
ijkFallbackTried = false;
|
||||
|
||||
if (binding != null) {
|
||||
binding.playerView.setVisibility(View.GONE);
|
||||
binding.flvTextureView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
TextureView view = binding.flvTextureView;
|
||||
TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(@NonNull android.graphics.SurfaceTexture surfaceTexture, int width, int height) {
|
||||
ijkSurface = new Surface(surfaceTexture);
|
||||
prepareIjk(flvUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(@NonNull android.graphics.SurfaceTexture surface, int width, int height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(@NonNull android.graphics.SurfaceTexture surface) {
|
||||
releaseIjkPlayer();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(@NonNull android.graphics.SurfaceTexture surface) {
|
||||
}
|
||||
};
|
||||
|
||||
view.setSurfaceTextureListener(listener);
|
||||
if (view.isAvailable() && view.getSurfaceTexture() != null) {
|
||||
ijkSurface = new Surface(view.getSurfaceTexture());
|
||||
prepareIjk(flvUrl);
|
||||
}
|
||||
}
|
||||
|
||||
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 -> {
|
||||
binding.offlineLayout.setVisibility(View.GONE);
|
||||
mp.start();
|
||||
addChatMessage(new ChatMessage("直播已连接,开始观看吧!", true));
|
||||
});
|
||||
|
||||
p.setOnErrorListener((IMediaPlayer mp, int what, int extra) -> {
|
||||
if (ijkFallbackTried || TextUtils.isEmpty(ijkFallbackHlsUrl)) {
|
||||
binding.offlineLayout.setVisibility(View.VISIBLE);
|
||||
return true;
|
||||
}
|
||||
ijkFallbackTried = true;
|
||||
startHls(ijkFallbackHlsUrl, null);
|
||||
return true;
|
||||
});
|
||||
|
||||
ijkPlayer = p;
|
||||
try {
|
||||
p.setSurface(ijkSurface);
|
||||
p.setDataSource(url);
|
||||
p.prepareAsync();
|
||||
} catch (Exception e) {
|
||||
if (!TextUtils.isEmpty(ijkFallbackHlsUrl)) {
|
||||
startHls(ijkFallbackHlsUrl, null);
|
||||
} else {
|
||||
binding.offlineLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureIjkLibsLoaded() {
|
||||
if (ijkLibLoaded) return;
|
||||
try {
|
||||
IjkMediaPlayer.loadLibrariesOnce(null);
|
||||
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
ijkLibLoaded = true;
|
||||
}
|
||||
|
||||
private void releasePlayer() {
|
||||
releaseExoPlayer();
|
||||
releaseIjkPlayer();
|
||||
if (binding != null) {
|
||||
binding.playerView.setPlayer(null);
|
||||
binding.flvTextureView.setVisibility(View.GONE);
|
||||
binding.playerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseExoPlayer() {
|
||||
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;
|
||||
}
|
||||
|
||||
private String getAltHlsUrl(String url) {
|
||||
|
|
|
|||
|
|
@ -472,7 +472,7 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/orbitContainer"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottomAppBar">
|
||||
app:layout_constraintBottom_toTopOf="@id/bottomNavInclude">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,16 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/flvTextureView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/playerView"
|
||||
android:layout_width="0dp"
|
||||
|
|
|
|||
|
|
@ -94,6 +94,12 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/topBar">
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/flvTextureView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/playerView"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@
|
|||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="ShapeAppearanceOverlay" parent="ShapeAppearanceOverlay.MaterialComponents" />
|
||||
<!-- 基础形状样式 -->
|
||||
<style name="ShapeAppearanceOverlay" parent="ShapeAppearance.MaterialComponents" />
|
||||
|
||||
<style name="ShapeAppearanceOverlay.MaterialComponents" />
|
||||
<style name="ShapeAppearanceOverlay.MaterialComponents" parent="ShapeAppearance.MaterialComponents" />
|
||||
|
||||
<style name="ShapeAppearanceOverlay.MaterialComponents.Circular" parent="ShapeAppearanceOverlay.MaterialComponents">
|
||||
<item name="cornerSize">50%</item>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=file:///D:/soft/gradle-8.1-bin.zip
|
||||
distributionUrl=file:///D:/soft/gradle-8.14-bin.zip
|
||||
networkTimeout=600000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ dependencyResolutionManagement {
|
|||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven(url = "https://jitpack.io")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user