网络视频流完善

yt_mpremote
liuguijing 2 weeks ago
parent dc4c10c9a7
commit f1c946172b

@ -10,6 +10,7 @@ def AppVersionName = AppMajorVersion + "." + AppMinorVersion + "." + AppBuildNum
def AppVersionCode = AppMajorVersion * 100000 + AppMinorVersion * 1000 + AppBuildNumber
android {
namespace "com.xypower.mpremote"
compileSdk 33
defaultConfig {
@ -78,20 +79,103 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10"
implementation "androidx.media3:media3-exoplayer:1.1.1"
implementation "androidx.media3:media3-exoplayer-dash:1.1.1"
implementation "androidx.media3:media3-ui:1.1.1"
implementation "androidx.media3:media3-datasource-rtmp:1.1.1"
implementation "androidx.media3:media3-exoplayer-rtsp:1.1.1"
// implementation 'io.vov.vitamio:vitamio:4.2.2'
// implementation 'com.google.android.exoplayer:exoplayer:2.14.0' // ExoPlayer
// // ExoPlayer
// implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1'
// implementation 'com.google.android.exoplayer:exoplayer-hls:2.19.1'
// implementation 'com.google.android.exoplayer:exoplayer-dash:2.19.1'
// implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.19.1'
//
//// RTSP
// implementation 'com.google.android.exoplayer:exoplayer-rtsp:2.19.1'
//
//// RTMP (ExoPlayer 2.19.1)
// implementation 'com.github.Piasy:ExoPlayerRtmp:v2.19.1.0'
//
//// H.265(HEVC) AV1
// implementation 'com.google.android.exoplayer:exoplayer-codec-av1:2.19.1'
// implementation "androidx.media3:media3-exoplayer:1.1.1"
// implementation "androidx.media3:media3-exoplayer-dash:1.1.1"
// implementation "androidx.media3:media3-ui:1.1.1"
// implementation "androidx.media3:media3-datasource-rtmp:1.1.1"
// implementation ("androidx.media3:media3-exoplayer-rtsp:1.1.1"){
// exclude group: 'com.google.android.exoplayer', module: 'exoplayer-core'
// }
// implementation 'androidx.media3:media3-decoder:1.1.1'
// Media3
// implementation "androidx.media3:media3-exoplayer:1.7.1"
// implementation "androidx.media3:media3-exoplayer-rtsp:1.7.1"
// implementation "androidx.media3:media3-ui:1.7.1"
// implementation "androidx.media3:media3-datasource-rtmp:1.7.1"
// // ExoPlayer
// configurations.all {
// exclude group: 'com.google.android.exoplayer', module: 'exoplayer-core'
// exclude group: 'com.google.android.exoplayer', module: 'exoplayer-dash'
// exclude group: 'com.google.android.exoplayer', module: 'exoplayer-ui'
// }
// implementation 'androidx.media3:media3-decoder-extension-ffmpeg:1.1.0'
// implementation 'androidx.media3:media3-decoder-extension-ffmpeg-full:1.1.1'
// FFmpeg H.265/HEVC
// implementation 'androidx.media3:media3-decoder-extension-ffmpeg-full:1.1.1'
// // 使 BOM Media3
// implementation platform('androidx.media3:media3-bom:1.1.1')
//
// //
// implementation 'androidx.media3:media3-exoplayer'
// implementation 'androidx.media3:media3-exoplayer-dash'
// implementation 'androidx.media3:media3-ui'
// implementation 'androidx.media3:media3-datasource-rtmp'
// implementation 'androidx.media3:media3-exoplayer-rtsp'
// implementation 'androidx.media3:media3-decoder'
// implementation 'androidx.media3:media3-decoder-extension-ffmpeg-full'
////// IJKPlayer
// implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
// implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8' // ARMv7a
// implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8' // ARM64
// implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8' // x86
//
// implementation 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8'
// implementation 'com.google.android.exoplayer:exoplayer-core:2.18.1'
// implementation 'com.google.android.exoplayer:exoplayer-hls:2.18.1' // HLS
// implementation 'com.google.android.exoplayer:exoplayer-dash:2.18.1' // DASH
// RTSP
// implementation 'com.googlecode.rtsp-client:rtsp-client:2.1.0'
// implementation 'com.github.videolan:vlc-android-sdk:3.3.10'
// implementation 'com.github.videolan:libvlc-android:3.4.11'
// 使ABIAPK
// implementation 'com.github.videolan:libvlc-android:3.4.11:arm64-v8a'
// ABI
// implementation 'com.github.videolan:libvlc-android:3.4.11:all'
// implementation 'org.videolan.android:libvlc-all:4.0.0-eap9'
implementation 'org.videolan.android:libvlc-all:3.6.0'
implementation 'com.github.bumptech.glide:glide:4.16.0'
// https://mvnrepository.com/artifact/dev.mobile/dadb
implementation 'dev.mobile:dadb:1.2.8'
implementation files('libs/common-release.aar')
// implementation project(path: ':dadb')
implementation 'org.videolan.android:libvlc-all:3.4.5'
// implementation 'org.videolan.android:libvlc-all:3.4.5'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

@ -20,4 +20,9 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keep class com.bumptech.glide.** { *; }
-dontwarn com.bumptech.glide.**
-dontwarn com.bumptech.glide.**
#-keep class tv.danmaku.ijk.media.player.** { *; }
#-keep class tv.danmaku.ijk.media.widget.** { *; }
# 保留 Media3 RTSP 相关类
-keep class androidx.media3.datasource.rtsp.** { *; }
-keep class androidx.media3.exoplayer.rtsp.** { *; }

@ -13,24 +13,27 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:extractNativeLibs="true"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MpRemote"
android:usesCleartextTraffic="true"
android:extractNativeLibs="true"
android:requestLegacyExternalStorage="true"
tools:targetApi="28">
<activity
android:name=".StreamActivity"
android:exported="false" />
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".SettingsActivity"
android:exported="false"

@ -0,0 +1,43 @@
//package com.xypower.mpremote;
//
//import androidx.media3.common.MediaItem;
//import androidx.media3.exoplayer.ExoPlayer;
//import androidx.media3.exoplayer.rtsp.RtspMediaSource;
//import androidx.media3.exoplayer.source.MediaSource;
//
//// 自定义 RtspMediaSource.Factory移除 trackID 参数
//public class CustomRtspMediaSourceFactory extends RtspMediaSource.Factory {
// public CustomRtspMediaSourceFactory() {
// super();
// }
//
// @Override
// public MediaSource createMediaSource(MediaItem mediaItem) {
// // 修改原始 URI移除 trackID 参数
// String originalUri = mediaItem.getUri().toString();
// String modifiedUri = removeTrackIdParameter(originalUri);
//
// // 使用修改后的 URI 创建新的 MediaItem
// MediaItem modifiedMediaItem = mediaItem.buildUpon()
// .setUri(modifiedUri)
// .build();
//
// return super.createMediaSource(modifiedMediaItem);
// }
//
// // 移除 URL 中的 trackID 参数
// private String removeTrackIdParameter(String url) {
// if (url.contains("trackID=")) {
// int index = url.indexOf("trackID=");
// int endIndex = url.indexOf("&", index);
// if (endIndex == -1) {
// // 如果 trackID 是最后一个参数,直接截断
// return url.substring(0, index - 1);
// } else {
// // 否则移除 trackID 参数段
// return url.substring(0, index) + url.substring(endIndex);
// }
// }
// return url;
// }
//}

@ -434,7 +434,34 @@ public class DeviceActivity extends AppCompatActivity implements View.OnClickLis
protected long takeVideo(int channel, int preset, final boolean photoOrVideo) {
// int cameraId = channel - 1;
//
// String leftTopOsd = "";
// if (mAppConfig != null) {
// leftTopOsd += mAppConfig.cmdid + " ";
// }
//
// // leftTopOsd = leftTopOsd.replaceAll("\\\\", "\\\\\\\\");
// // leftTopOsd = leftTopOsd.replaceAll(" ", "\\\\ ");
// byte[] leftTopOsdBytes = leftTopOsd.getBytes(StandardCharsets.UTF_8);
//
// String usb = (channel == 4) ? "true" : "false";
// JSONObject channelJson = (JSONObject) mChannelCfgs.get(Integer.valueOf(channel));
// if (channelJson != null) {
// int usbCamera = channelJson.optInt("usbCamera", 0);
// usb = (usbCamera != 0) ? "true" : "false";
// cameraId = channelJson.optInt("cameraId", cameraId);
// }
// // content update --uri content://com.xypower.mpapp.provider/importPriKey
// final String cmd = "content update --uri content://" + Constants.PACKAGE_NAME_MP + ".provider/takePhoto " +" --bind usb:b:" + usb + " --bind cameraId:i:" + Integer.toString(cameraId) + " --bind channel:i:" + Integer.toString(channel) + " --bind preset:i:" + Integer.toString(preset) + " --bind leftTopOsd:s:" + Base64.encodeToString(leftTopOsdBytes, Base64.DEFAULT | Base64.NO_WRAP) + "";
// // adbShellResponse = null;
//
// photoDialog = AlertDialogUtils.show(this, "照片或视频获取中");
showStreaming(channel, channel - 1, "rtmp://" + mDeviceIp + "/live/0", localIp);
// showStreaming(channel, channel - 1, "rtsp://61.169.135.146:1554/live/11", localIp);
return 0;
/*
@ -682,9 +709,12 @@ public class DeviceActivity extends AppCompatActivity implements View.OnClickLis
finish();
break;
case R.id.refresh:
loadDeviceInfo(false);
// loadDeviceInfo(false)
// ;
Intent intent = new Intent(this, StreamActivity.class);
// intent.putExtra("info", info);
startActivity(intent);
break;
}
}

@ -0,0 +1,47 @@
//package com.xypower.mpremote;
//
//
//import android.net.Uri;
//
//import androidx.media3.datasource.DataSource;
//import androidx.media3.datasource.DataSpec;
//import androidx.media3.datasource.TransferListener;
//import androidx.media3.exoplayer.rtsp.RtspMediaSource;
//
//import java.net.URI;
//import java.net.URISyntaxException;
//
//
//// 自定义 RtspDataSourceFactory去除 trackid 参数
//public class RtspDataSourceFactoryWithoutTrackId extends RtspMediaSource.Factory {
// private final DataSource.Factory upstreamFactory;
//
// public RtspDataSourceFactoryWithoutTrackId(DataSource.Factory upstreamFactory) {
// this.upstreamFactory = upstreamFactory;
// }
//
// @Override
// protected RtspDataSource createDataSourceInternal(TransferListener listener, DataSpec dataSpec) {
// // 处理 DataSpec 中的 URI去除 trackid 参数
// Uri uri = dataSpec.uri;
// if (uri != null) {
// try {
// URI javaUri = new URI(uri.toString());
// String query = javaUri.getQuery();
// if (query != null) {
// // 去除 trackid 参数
// query = query.replaceAll("trackid=[^&]*&?", "");
// if (query.endsWith("&")) {
// query = query.substring(0, query.length() - 1);
// }
// javaUri = new URI(javaUri.getScheme(), javaUri.getAuthority(), javaUri.getPath(), query, javaUri.getFragment());
// uri = Uri.parse(javaUri.toString());
// dataSpec = dataSpec.buildUpon().setUri(uri).build();
// }
// } catch (URISyntaxException e) {
// e.printStackTrace();
// }
// }
// return super.createDataSourceInternal(listener, dataSpec);
// }
//}

@ -0,0 +1,327 @@
//package com.xypower.mpremote;
//
//import android.app.AlertDialog;
//import android.content.Intent;
//import android.os.Bundle;
//import android.os.Handler;
//import android.util.Log;
//import android.view.Menu;
//import android.view.MenuItem;
//import android.view.View;
//import android.widget.Toast;
//
//import androidx.annotation.NonNull;
//import androidx.annotation.OptIn;
//import androidx.appcompat.app.AppCompatActivity;
//import androidx.media3.common.MediaItem;
//import androidx.media3.common.PlaybackException;
//import androidx.media3.common.Player;
//import androidx.media3.common.util.UnstableApi;
//import androidx.media3.common.util.Util;
//import androidx.media3.exoplayer.DefaultLoadControl;
//import androidx.media3.exoplayer.DefaultRenderersFactory;
//import androidx.media3.exoplayer.ExoPlayer;
//import androidx.media3.exoplayer.RenderersFactory;
//import androidx.media3.exoplayer.rtsp.RtspMediaSource;
//import androidx.media3.ui.PlayerView;
//
//import com.xypower.mpremote.databinding.ActivityStreamBinding;
//import com.xypower.mpremote.utils.AdbUtils;
//import com.xypower.mpremote.utils.AlertDialogUtils;
//import com.xypower.mpremote.utils.AppUtils;
//
//import dadb.AdbShellResponse;
//import dadb.Dadb;
//
//
//@UnstableApi public class Stream2Activity extends AppCompatActivity implements View.OnClickListener {
//
// private static final String TAG = "STRM";
// private static final int MAX_RETRIES = 5;
//
// private ExoPlayer player;
// private String mDeviceIp;
// private Handler retryHandler = new Handler();
// @NonNull
// private ActivityStreamBinding binding;
// private String localIp;
// private int cameraId;
// private int anInt;
// private int rotation;
// private int netCamera;
// private int vendor;
// private int retryCount;
// private static final long INITIAL_RETRY_DELAY_MS = 1000; // 初始重试延迟
// private static final long MAX_RETRY_DELAY_MS = 15000; // 最大重试延迟
// private static final float RETRY_BACKOFF_MULTIPLIER = 1.5f; // 退避乘数
// private long currentRetryDelay = INITIAL_RETRY_DELAY_MS;
// private boolean isBuffering = false;
// private long bufferingStartTime = 0;
// private static final long BUFFERING_TIMEOUT_MS = 100000; // 10秒缓冲超时
// private String RTMP_URL;
// private PlayerView playerView;
// private AlertDialog alertDialog;
//
// @Override
// protected void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// binding = ActivityStreamBinding.inflate(getLayoutInflater());
// setContentView(binding.getRoot());
// initHandler();
// initIntent();
// initView();
// initEvent();
// }
//
// private void initEvent() {
// Runnable runnable = new Runnable() {
// @Override
// public void run() {
// initializePlayer();
// }
// };
// runnable.run();
// }
//
// private void initHandler() {
// }
//
// private void initIntent() {
// Intent intent = getIntent();
// mDeviceIp = intent.getStringExtra("deviceIp");
// localIp = intent.getStringExtra("localIp");
// cameraId = intent.getIntExtra("cameraId", 0);
// anInt = intent.getIntExtra("channel", 1);
// rotation = intent.getIntExtra("rotation", -1);
// netCamera = intent.getIntExtra("netCamera", 0);
// vendor = intent.getIntExtra("vendor", 0);
// }
//
// private void initView() {
// String versionName = AppUtils.getAppNameWithVersion(this);
// binding.toolbar.title.setText(versionName);
// binding.toolbar.refresh.setVisibility(View.VISIBLE);
// binding.toolbar.back.setOnClickListener(this);
// binding.toolbar.refresh.setOnClickListener(this);
// playerView = binding.playerView;
// }
//
// private void startStreaming(final String cmd, final Runnable runnable) {
// Thread th = new Thread(new Runnable() {
// @Override
// public void run() {
// Dadb adb = null;
// try {
// Log.i(TAG, "Start connecting " + mDeviceIp);
// adb = AdbManager.getAdb(mDeviceIp, AdbUtils.DEFAULT_ADB_PORT);
// Log.i(TAG, "Finish connecting " + mDeviceIp);
// if (adb == null) {
// retryHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// Toast.makeText(Stream2Activity.this, R.string.err_dev_failed_to_connect, Toast.LENGTH_LONG).show();
// }
// }, 100);
// return;
// }
// AdbShellResponse adbShellResponse = null;
// try {
// adbShellResponse = adb.shell("am force-stop com.xypower.mplive");
// Thread.sleep(200);
// } catch (Exception ex) {
// ex.printStackTrace();
// }
// try {
// adbShellResponse = adb.shell(cmd);
// } catch (Exception ex) {
// ex.printStackTrace();
// }
// if (adbShellResponse != null) {
// if (adbShellResponse.getExitCode() == 0) {
// try {
// Thread.sleep(5000);
// } catch (Exception ex) {
// }
// runOnUiThread(runnable);
// }
// }
// } catch (Exception ex) {
// ex.printStackTrace();
// }
// }
// });
// th.start();
// }
//
// private void stopStreaming() {
// String cmd = "am force-stop com.xypower.mplive";
// Thread th = new Thread(new Runnable() {
// @Override
// public void run() {
// Dadb adb = null;
// try {
// Log.i(TAG, "Start connecting " + mDeviceIp);
// adb = AdbManager.getAdb(mDeviceIp, AdbUtils.DEFAULT_ADB_PORT);
// Log.i(TAG, "Finish connecting " + mDeviceIp);
// if (adb == null) {
// return;
// }
// AdbShellResponse adbShellResponse = null;
// try {
// adbShellResponse = adb.shell(cmd);
// } catch (Exception ex) {
// ex.printStackTrace();
// }
// if (adbShellResponse != null) {
// if (adbShellResponse.getExitCode() == 0) {
// }
// }
// } catch (Exception ex) {
// ex.printStackTrace();
// }
// }
// });
// th.start();
// }
//
// private void initializePlayer() {
// if (player == null) {
//
// // 创建一个默认的 RenderersFactory
// RenderersFactory renderersFactory = new DefaultRenderersFactory(this)
// .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
// player = new ExoPlayer.Builder(this).setRenderersFactory(renderersFactory)
// .setLoadControl(new DefaultLoadControl()).build();
// playerView.setPlayer(player);
// player.addListener(new Player.Listener() {
// @Override
// public void onPlayerError(PlaybackException error) {
// Log.e(TAG, "播放错误: " + error.getMessage());
// Log.e(TAG, "播放错误: " + error.getCause());
// Log.e(TAG, "播放错误: " + error.getErrorCodeName());
// handlePlaybackError();
// }
//
// @Override
// public void onPlaybackStateChanged(int state) {
// if (state == Player.STATE_BUFFERING) {
// if (!isBuffering) {
// isBuffering = true;
// bufferingStartTime = System.currentTimeMillis();
// // 启动缓冲超时检查
// retryHandler.postDelayed(bufferingTimeoutRunnable, BUFFERING_TIMEOUT_MS);
// }
// } else if (state == Player.STATE_READY) {
// AlertDialogUtils.dismiss(alertDialog);
// isBuffering = false;
// retryHandler.removeCallbacks(bufferingTimeoutRunnable);
// retryCount = 0;
// currentRetryDelay = INITIAL_RETRY_DELAY_MS;
// Log.d(TAG, "播放器准备就绪");
// }
// }
// });
// }
// startPlayback();
// }
//
// private Runnable bufferingTimeoutRunnable = new Runnable() {
// @Override
// public void run() {
// if (isBuffering) {
// Log.w(TAG, "缓冲超时,触发重连");
// handlePlaybackError();
// }
// }
// };
//
// private void startPlayback() {
// if (player == null) return;
// Log.d(TAG, "开始播放,重试次数: " + retryCount);
// MediaItem mediaItem = MediaItem.fromUri("rtsp://61.169.135.146:1554/live/abc");
// RtspMediaSource mediaSource = new RtspMediaSource.Factory().createMediaSource(mediaItem);
// player.setMediaItem(mediaItem);
// player.prepare();
// player.setPlayWhenReady(true);
// }
//
// private void handlePlaybackError() {
// releasePlayerInternal();
// if (retryCount < MAX_RETRIES) {
// retryCount++;
// Log.d(TAG, "准备重试 (" + retryCount + "), 延迟: " + currentRetryDelay + "ms");
// retryHandler.postDelayed(() -> {
// initializePlayer();
// // 增加下次重试的延迟时间(使用退避算法)
// currentRetryDelay = Math.min((long) (currentRetryDelay * RETRY_BACKOFF_MULTIPLIER), MAX_RETRY_DELAY_MS);
// }, currentRetryDelay);
// }
// }
//
// private void releasePlayerInternal() {
// if (player != null) {
// player.release();
// player = null;
// isBuffering = false;
// retryHandler.removeCallbacks(bufferingTimeoutRunnable);
// }
// }
//
// @Override
// protected void onDestroy() {
// super.onDestroy();
// AlertDialogUtils.dismiss(alertDialog);
// releasePlayerInternal();
// stopStreaming();
// }
//
// @OptIn(markerClass = UnstableApi.class)
// @Override
// protected void onPause() {
// super.onPause();
// if (Util.SDK_INT < 24) {
// releasePlayerInternal();
// }
// }
//
// @OptIn(markerClass = UnstableApi.class)
// @Override
// protected void onStop() {
// super.onStop();
// if (Util.SDK_INT >= 24) {
// releasePlayerInternal();
// }
// }
//
// @Override
// public boolean onCreateOptionsMenu(Menu menu) {
// // Inflate the menu; this adds items to the action bar if it is present.
// getMenuInflater().inflate(R.menu.stream_activity_actions, menu);
// return true;
// }
//
// @Override
// public boolean onOptionsItemSelected(MenuItem item) {
// int id = item.getItemId();
// switch (id) {
// case android.R.id.home: //返回键的id
// initializePlayer();
// return false;
// default:
// break;
// }
// return super.onOptionsItemSelected(item);
// }
//
// @Override
// public void onClick(View v) {
// switch (v.getId()) {
// case R.id.back:
// finish();
// break;
// case R.id.refresh:
// initEvent();
// break;
// }
// }
//}

@ -1,64 +1,64 @@
package com.xypower.mpremote;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.MediaItem;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.rtmp.RtmpDataSource;
import androidx.media3.exoplayer.DefaultLoadControl;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
import androidx.media3.ui.PlayerView;
import android.app.AlertDialog;
import android.content.Intent;
import android.graphics.SurfaceTexture;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import com.xypower.mpremote.databinding.ActivityStreamBinding;
import com.xypower.mpremote.utils.AdbUtils;
import com.xypower.mpremote.utils.AlertDialogUtils;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
import org.videolan.libvlc.interfaces.IVLCVout;
import dadb.AdbShellResponse;
import dadb.Dadb;
import java.util.ArrayList;
public class StreamActivity extends AppCompatActivity implements View.OnClickListener {
import android.Manifest;
import android.content.pm.PackageManager;
import android.view.TextureView;
private static final String TAG = "STRM";
private static final int MAX_RETRIES = 5;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
private ExoPlayer player;
private String mDeviceIp;
private Handler retryHandler = new Handler();
@NonNull
import dadb.AdbShellResponse;
import dadb.Dadb;
public class StreamActivity extends AppCompatActivity implements IVLCVout.Callback, TextureView.SurfaceTextureListener {
private static final String TAG = "VLCPlayer";
private static final int PERMISSION_REQUEST_CODE = 1001;
private static String pushUrl = "";
private static final int RECONNECT_DELAY = 5000; // 重连延迟5秒
private int reconnectAttempts = 0;
private static final int MAX_RECONNECT_ATTEMPTS = 50; // 最大重连次数
private TextureView textureView;
private LibVLC libVLC;
private MediaPlayer mediaPlayer;
private SurfaceTexture surfaceTexture;
private Handler handler;
private boolean isSurfaceReady = false;
private boolean isPlayerReady = false;
private com.xypower.mpremote.databinding.ActivityStreamBinding binding;
private String localIp;
private String mDeviceIp;
private int cameraId;
private int anInt;
private int rotation;
private int netCamera;
private int vendor;
private int retryCount;
private static final long INITIAL_RETRY_DELAY_MS = 1000; // 初始重试延迟
private static final long MAX_RETRY_DELAY_MS = 15000; // 最大重试延迟
private static final float RETRY_BACKOFF_MULTIPLIER = 1.5f; // 退避乘数
private long currentRetryDelay = INITIAL_RETRY_DELAY_MS;
private boolean isBuffering = false;
private long bufferingStartTime = 0;
private static final long BUFFERING_TIMEOUT_MS = 10000; // 10秒缓冲超时
private String RTMP_URL;
private PlayerView playerView;
private int channel;
private AlertDialog alertDialog;
@Override
@ -66,45 +66,72 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
super.onCreate(savedInstanceState);
binding = ActivityStreamBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
initHandler();
initIntent();
initView();
initHandler();
initEvent();
// 检查并请求权限
if (checkPermissions()) {
// initializePlayer();
} else {
requestPermissions();
}
}
private void initEvent() {
RTMP_URL = "rtmp://" + mDeviceIp + "/live/0";
String cmd = "am start -n com.xypower.mplive/com.xypower.mplive.MainActivity" + " --ei cameraId " + Integer.toString(cameraId) + " --ei rotation " + Integer.toString(rotation) + " --ei netCamera " + Integer.toString(netCamera) + " --ei vendor " + Integer.toString(vendor) + " --ei autoStart 1" + " --es url \"" + RTMP_URL + "\"";
Runnable runnable = new Runnable() {
private void initHandler() {
handler = new Handler(Looper.getMainLooper()) {
@Override
public void run() {
initializePlayer();
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1:
AlertDialogUtils.dismiss(alertDialog);
break;
}
}
};
alertDialog = AlertDialogUtils.show(this, "视频加载中");
startStreaming(cmd, runnable);
}
private void initHandler() {
// mHandler = new Handler();
private void initView() {
textureView = binding.textureView;
textureView.setSurfaceTextureListener(this);
}
private void initIntent() {
Intent intent = getIntent();
mDeviceIp = intent.getStringExtra("deviceIp");
localIp = intent.getStringExtra("localIp");
cameraId = intent.getIntExtra("cameraId", 0);
anInt = intent.getIntExtra("channel", 1);
channel = intent.getIntExtra("channel", 1);
rotation = intent.getIntExtra("rotation", -1);
netCamera = intent.getIntExtra("netCamera", 0);
vendor = intent.getIntExtra("vendor", 0);
}
private void initView() {
binding.toolbar.refresh.setVisibility(View.VISIBLE);
binding.toolbar.back.setOnClickListener(this);
binding.toolbar.refresh.setOnClickListener(this);
playerView = binding.playerView;
private void initEvent() {
if (netCamera == 0) {
pushUrl = "rtmp://127.0.0.1/live/" + channel;
} else {
pushUrl = "rtsp://127.0.0.1:8554/live/" + channel;
}
if (TextUtils.isEmpty(pushUrl)) {
handler.post(() -> Toast.makeText(StreamActivity.this, "未找到推流路径", Toast.LENGTH_LONG).show());
} else {
String cmd = "am start -n com.xypower.mplive/com.xypower.mplive.MainActivity" + " --ei cameraId " + Integer.toString(cameraId) +" --ei channel " + Integer.toString(channel)+ " --ei rotation " + Integer.toString(rotation) + " --ei netCamera " + Integer.toString(netCamera) + " --ei vendor " + Integer.toString(vendor) + " --ei autoStart 1" + " --es url \"" + pushUrl + "\"";
Runnable runnable = new Runnable() {
@Override
public void run() {
initializePlayer();
}
};
alertDialog = AlertDialogUtils.show(this, "视频加载中");
startStreaming(cmd, runnable);
// runOnUiThread(runnable);
// new Thread(runnable).start();
}
}
private void startStreaming(final String cmd, final Runnable runnable) {
@ -117,7 +144,7 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
adb = AdbManager.getAdb(mDeviceIp, AdbUtils.DEFAULT_ADB_PORT);
Log.i(TAG, "Finish connecting " + mDeviceIp);
if (adb == null) {
retryHandler.postDelayed(new Runnable() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(StreamActivity.this, R.string.err_dev_failed_to_connect, Toast.LENGTH_LONG).show();
@ -140,7 +167,7 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
if (adbShellResponse != null) {
if (adbShellResponse.getExitCode() == 0) {
try {
Thread.sleep(5000);
Thread.sleep(150000);
} catch (Exception ex) {
}
runOnUiThread(runnable);
@ -154,30 +181,26 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
th.start();
}
private void stopStreaming() {
String cmd = "am force-stop com.xypower.mplive";
Thread th = new Thread(new Runnable() {
@Override
public void run() {
Dadb adb = null;
try {
Log.i(TAG, "Start connecting " + mDeviceIp);
adb = AdbManager.getAdb(mDeviceIp, AdbUtils.DEFAULT_ADB_PORT);
Log.i(TAG, "Finish connecting " + mDeviceIp);
if (adb == null) {
return;
}
AdbShellResponse adbShellResponse = null;
try {
adbShellResponse = adb.shell(cmd);
} catch (Exception ex) {
ex.printStackTrace();
}
if (adbShellResponse != null) {
if (adbShellResponse.getExitCode() == 0) {
}
@ -188,154 +211,248 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
}
});
th.start();
}
private void initializePlayer() {
if (player == null) {
player = new ExoPlayer.Builder(this).setLoadControl(new DefaultLoadControl.Builder().setBufferDurationsMs(5000, // minBufferMs
10000, // maxBufferMs
500, // bufferForPlaybackMs
500 // bufferForPlaybackAfterRebufferMs
).setPrioritizeTimeOverSizeThresholds(true).build()).build();
playerView.setPlayer(player);
player.addListener(new Player.Listener() {
@Override
public void onPlayerError(PlaybackException error) {
Log.e(TAG, "播放错误: " + error.getMessage());
handlePlaybackError();
}
private boolean checkPermissions() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET) == PackageManager.PERMISSION_GRANTED;
}
@Override
public void onPlaybackStateChanged(int state) {
if (state == Player.STATE_BUFFERING) {
if (!isBuffering) {
isBuffering = true;
bufferingStartTime = System.currentTimeMillis();
// 启动缓冲超时检查
retryHandler.postDelayed(bufferingTimeoutRunnable, BUFFERING_TIMEOUT_MS);
}
} else if (state == Player.STATE_READY) {
AlertDialogUtils.dismiss(alertDialog);
isBuffering = false;
retryHandler.removeCallbacks(bufferingTimeoutRunnable);
retryCount = 0;
currentRetryDelay = INITIAL_RETRY_DELAY_MS;
Log.d(TAG, "播放器准备就绪");
}
}
});
}
startPlayback();
private void requestPermissions() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.INTERNET},
PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// initializePlayer();
} else {
Toast.makeText(this, "需要存储权限才能播放视频", Toast.LENGTH_SHORT).show();
finish();
}
}
}
private Runnable bufferingTimeoutRunnable = new Runnable() {
@Override
public void run() {
if (isBuffering) {
Log.w(TAG, "缓冲超时,触发重连");
handlePlaybackError();
private void initializePlayer() {
// 初始化 LibVLC
ArrayList<String> options = new ArrayList<>();
options.add("--aout=opensles");
options.add("--audio-time-stretch");
// options.add("--avcodec-hw=none"); // 禁用硬件加速,强制软件解码
options.add("-vvv"); // 详细日志
// RTSP 特定参数
options.add("--rtsp-tcp"); // 使用 TCP 传输,提高稳定性
options.add("--network-caching=500"); // 网络缓存时间(毫秒)
options.add("--live-caching=500"); // 直播缓存时间
libVLC = new LibVLC(this, options);
mediaPlayer = new MediaPlayer(libVLC);
mediaPlayer.setEventListener(event -> {
switch (event.type) {
case MediaPlayer.Event.Playing:
Log.d(TAG, "播放中");
handler.sendMessage(handler.obtainMessage(1));
break;
case MediaPlayer.Event.Paused:
Log.d(TAG, "已暂停");
break;
case MediaPlayer.Event.Stopped:
Log.d(TAG, "已停止");
break;
case MediaPlayer.Event.EncounteredError:
Log.e(TAG, "播放错误: " + mediaPlayer.getMedia().getState());
handler.post(() -> Toast.makeText(StreamActivity.this, "播放失败: " + getErrorString(mediaPlayer.getMedia().getState()), Toast.LENGTH_LONG).show());
attemptReconnect();
break;
case MediaPlayer.Event.EndReached:
Log.d(TAG, "播放结束");
handler.post(() -> Toast.makeText(StreamActivity.this, "播放结束", Toast.LENGTH_SHORT).show());
attemptReconnect();
break;
}
});
isPlayerReady = true;
if (isSurfaceReady) {
loadMedia();
}
};
private void startPlayback() {
if (player == null) return;
Log.d(TAG, "开始播放,重试次数: " + retryCount);
MediaItem mediaItem = MediaItem.fromUri(RTMP_URL);
ProgressiveMediaSource videoSource = new ProgressiveMediaSource.Factory(new RtmpDataSource.Factory()).createMediaSource(mediaItem);
player.setMediaSource(videoSource);
// player.setMediaItem(mediaItem);
player.prepare();
player.setPlayWhenReady(true);
}
private void handlePlaybackError() {
releasePlayerInternal();
if (retryCount < MAX_RETRIES) {
retryCount++;
Log.d(TAG, "准备重试 (" + retryCount + "), 延迟: " + currentRetryDelay + "ms");
retryHandler.postDelayed(() -> {
private void attemptReconnect() {
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
reconnectAttempts++;
Log.d(TAG, "尝试重连 (第" + reconnectAttempts + "次)");
handler.postDelayed(() -> {
releasePlayer();
initializePlayer();
// 增加下次重试的延迟时间(使用退避算法)
currentRetryDelay = Math.min((long) (currentRetryDelay * RETRY_BACKOFF_MULTIPLIER), MAX_RETRY_DELAY_MS);
}, currentRetryDelay);
}, RECONNECT_DELAY);
} else {
Log.e(TAG, "达到最大重连次数,停止尝试");
handler.post(() -> Toast.makeText(StreamActivity.this, "无法连接到直播流,已达到最大重试次数", Toast.LENGTH_LONG).show());
}
}
private void releasePlayerInternal() {
if (player != null) {
player.release();
player = null;
isBuffering = false;
retryHandler.removeCallbacks(bufferingTimeoutRunnable);
private void loadMedia() {
if (mediaPlayer == null || surfaceTexture == null) return;
try {
// String rtspUrl = "rtsp://61.169.135.146:1554/live/ab";
// String rtspUrl = "rtsp://61.169.135.146:1554/live/abc";
// String rtspUrl = "rtmp://61.169.135.146/live/cc";
// String rtspUrl = "rtsp://192.168.43.1:8554/live/7";
// String encodedUrl = java.net.URLEncoder.encode(rtspUrl, "UTF-8");
// encodedUrl = encodedUrl.replaceAll("\\+", "%20"); // 将编码后的 + 替换为 %20
String rtspUrl = null;
if (netCamera == 0) {
rtspUrl = "rtmp://192.168.43.1/live/" + channel;
} else {
rtspUrl = "rtsp://192.168.43.1:8554/live/" + channel;
}
// 使用公开测试视频流
Media media = new Media(libVLC, Uri.parse(rtspUrl));
// 或者使用本地资源
// Media media = new Media(libVLC, Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.sample));
Log.d(TAG, "创建 Media 对象后的地址: " + media.getUri());
mediaPlayer.setMedia(media);
// 设置视频输出
IVLCVout vout = mediaPlayer.getVLCVout();
vout.setVideoSurface(surfaceTexture);
vout.addCallback(this);
vout.attachViews();
mediaPlayer.setAspectRatio(null); // 设置为 null 以自适应屏幕
mediaPlayer.setScale(0); // 设置缩放比例为 0 以自适应屏幕
// 设置视频缩放模式为居中
mediaPlayer.setVideoScale(MediaPlayer.ScaleType.SURFACE_BEST_FIT);
// 开始播放
mediaPlayer.play();
} catch (Exception e) {
Log.e(TAG, "加载媒体失败: " + e.getMessage());
Toast.makeText(this, "加载媒体失败", Toast.LENGTH_SHORT).show();
attemptReconnect();
}
}
private String getErrorString(int state) {
switch (state) {
case Media.State.Error:
return "未知错误";
// case Media.State.Invalid:
// return "无效媒体";
case Media.State.NothingSpecial:
return "未初始化";
case Media.State.Opening:
return "正在打开";
// case Media.State.Buffering:
// return "缓冲中";
case Media.State.Playing:
return "播放中";
case Media.State.Paused:
return "已暂停";
case Media.State.Stopped:
return "已停止";
case Media.State.Ended:
return "播放结束";
default:
return "状态: " + state;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
AlertDialogUtils.dismiss(alertDialog);
releasePlayerInternal();
stopStreaming();
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Log.d(TAG, "SurfaceTexture 可用");
surfaceTexture = surface;
isSurfaceReady = true;
if (isPlayerReady) {
loadMedia();
}
}
@OptIn(markerClass = UnstableApi.class)
@Override
protected void onPause() {
super.onPause();
if (Util.SDK_INT < 24) {
releasePlayerInternal();
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
Log.d(TAG, "SurfaceTexture 尺寸改变: " + width + "x" + height);
if (mediaPlayer != null) {
mediaPlayer.getVLCVout().setWindowSize(width, height);
// 设置视频缩放模式
mediaPlayer.setAspectRatio(null); // 自适应屏幕
mediaPlayer.setScale(0); // 自动缩放
}
}
@OptIn(markerClass = UnstableApi.class)
@Override
protected void onStop() {
super.onStop();
if (Util.SDK_INT >= 24) {
releasePlayerInternal();
}
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
Log.d(TAG, "SurfaceTexture 销毁");
releasePlayer();
isSurfaceReady = false;
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// 每次 SurfaceTexture 更新时调用
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.stream_activity_actions, menu);
return true;
public void onSurfacesCreated(IVLCVout vout) {
Log.d(TAG, "VLC 表面创建完成");
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home: //返回键的id
this.finish();
return false;
case R.id.action_play:
// scanWifi();
initializePlayer();
break;
default:
break;
public void onSurfacesDestroyed(IVLCVout vout) {
Log.d(TAG, "VLC 表面销毁");
}
@Override
protected void onResume() {
super.onResume();
if (isPlayerReady && !mediaPlayer.isPlaying() && isSurfaceReady) {
loadMedia();
}
}
return super.onOptionsItemSelected(item);
@Override
protected void onPause() {
super.onPause();
if (mediaPlayer != null) {
mediaPlayer.pause();
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.back:
finish();
break;
case R.id.refresh:
initEvent();
break;
protected void onStop() {
super.onStop();
if (mediaPlayer != null) {
mediaPlayer.stop();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
AlertDialogUtils.dismiss(alertDialog);
releasePlayer();
stopStreaming();
}
private void releasePlayer() {
if (mediaPlayer != null) {
IVLCVout vout = mediaPlayer.getVLCVout();
vout.detachViews();
vout.removeCallback(this);
mediaPlayer.release();
mediaPlayer = null;
}
if (libVLC != null) {
libVLC.release();
libVLC = null;
}
isPlayerReady = false;
}
}

@ -2,10 +2,6 @@ package com.xypower.mpremote;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Player;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.ui.PlayerView;
import android.content.Intent;
import android.content.res.Configuration;
@ -19,7 +15,6 @@ import java.io.File;
public class VideoActivity extends AppCompatActivity {
private ExoPlayer exoPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -34,14 +29,6 @@ public class VideoActivity extends AppCompatActivity {
Intent intent = getIntent();
String path = intent.getStringExtra("path");
if (path != null) {
PlayerView playerView = (PlayerView)findViewById(R.id.playerView);
exoPlayer = new ExoPlayer.Builder(this).build();
playerView.setPlayer(exoPlayer);
MediaItem mediaItem = MediaItem.fromUri(Uri.fromFile(new File(path)));
exoPlayer.addMediaItem(mediaItem);
exoPlayer.prepare();
exoPlayer.play();
}
@ -50,8 +37,6 @@ public class VideoActivity extends AppCompatActivity {
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
PlayerView playerView = (PlayerView)findViewById(R.id.playerView);
playerView.requestLayout();
}
@Override

@ -62,7 +62,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewVideo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:layout_marginRight="20dp"

@ -1,35 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".StreamActivity">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<!-- <SurfaceView-->
<!-- android:id="@+id/playerView"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="0dp"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintTop_toBottomOf="@+id/toolbar" />-->
<!-- <SurfaceView-->
<!-- android:id="@+id/playerView"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="0dp"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintTop_toBottomOf="@+id/toolbar" />-->
<TextureView
android:id="@+id/textureView"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<!-- <androidx.media3.ui.PlayerView-->
<!-- android:id="@+id/playerView"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="0dip"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintTop_toBottomOf="@+id/toolbar"-->
<!-- app:resize_mode="fit"-->
<!-- app:show_buffering="always"-->
<!-- app:show_timeout="5000"-->
<!-- app:use_controller="false"-->
<!-- tools:ignore="MissingConstraints" />-->
<androidx.media3.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="0dip"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:resize_mode="fit"
app:show_buffering="always"
app:show_timeout="5000"
app:use_controller="false"
tools:ignore="MissingConstraints" />
<!-- <tv.danmaku.ijk.media.widget.IjkVideoView-->
<!-- android:id="@+id/video_view"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent" />-->
<!-- <FrameLayout-->
<!-- android:id="@+id/video_container"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent" />-->
<!-- <ImageButton-->
<!-- android:id="@+id/center"-->
@ -79,4 +95,4 @@
<!-- app:layout_constraintTop_toBottomOf="@+id/center" />-->
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

@ -6,13 +6,13 @@
android:layout_height="match_parent"
tools:context=".VideoActivity">
<androidx.media3.ui.PlayerView
android:id="@+id/playerView"
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" />
<!-- <androidx.media3.ui.PlayerView-->
<!-- android:id="@+id/playerView"-->
<!-- 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" />-->
</androidx.constraintlayout.widget.ConstraintLayout>

@ -18,4 +18,5 @@ android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonTransitiveRClass=true
android.enableJetifier=true

@ -1,22 +1,24 @@
pluginManagement {
repositories {
google()
maven { url 'https://jitpack.io' }
maven{ url 'https://maven.aliyun.com/repository/google'}
maven{ url 'https://maven.aliyun.com/repository/gradle-plugin'}
maven{ url 'https://maven.aliyun.com/repository/public'}
maven{ url 'https://maven.aliyun.com/repository/jcenter'}
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
maven { url 'https://jitpack.io' }
maven{ url 'https://maven.aliyun.com/repository/google'}
maven{ url 'https://maven.aliyun.com/repository/gradle-plugin'}
maven{ url 'https://maven.aliyun.com/repository/public'}
maven{ url 'https://maven.aliyun.com/repository/jcenter'}
google()
mavenCentral()
}
}

Loading…
Cancel
Save