|
|
|
@ -2,7 +2,6 @@ package com.xypower.mpremote;
|
|
|
|
|
|
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
|
import androidx.annotation.OptIn;
|
|
|
|
|
import androidx.appcompat.app.ActionBar;
|
|
|
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
|
|
|
import androidx.media3.common.MediaItem;
|
|
|
|
|
import androidx.media3.common.PlaybackException;
|
|
|
|
@ -18,26 +17,25 @@ import androidx.media3.ui.PlayerView;
|
|
|
|
|
import android.content.Intent;
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
import android.os.Bundle;
|
|
|
|
|
import android.os.Environment;
|
|
|
|
|
import android.os.Handler;
|
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
import android.view.Menu;
|
|
|
|
|
import android.view.MenuItem;
|
|
|
|
|
import android.view.Surface;
|
|
|
|
|
import android.view.SurfaceHolder;
|
|
|
|
|
import android.view.SurfaceView;
|
|
|
|
|
import android.view.View;
|
|
|
|
|
import android.widget.Toast;
|
|
|
|
|
|
|
|
|
|
import com.xypower.common.FilesUtils;
|
|
|
|
|
import com.xypower.mpremote.databinding.ActivityMainBinding;
|
|
|
|
|
import com.xypower.mpremote.databinding.ActivityStreamBinding;
|
|
|
|
|
import com.xypower.mpremote.utils.AdbUtils;
|
|
|
|
|
import com.xypower.mpremote.zlmediakit.ZLMediaKit;
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
import org.videolan.libvlc.LibVLC;
|
|
|
|
|
import org.videolan.libvlc.Media;
|
|
|
|
|
import org.videolan.libvlc.MediaPlayer;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
|
|
|
|
|
import dadb.AdbKeyPair;
|
|
|
|
|
import dadb.AdbShellResponse;
|
|
|
|
|
import dadb.Dadb;
|
|
|
|
|
|
|
|
|
@ -47,9 +45,9 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
|
|
|
|
|
private static final String TAG = "STRM";
|
|
|
|
|
private static final int MAX_RETRIES = 5;
|
|
|
|
|
|
|
|
|
|
private ExoPlayer exoPlayer;
|
|
|
|
|
private ExoPlayer player;
|
|
|
|
|
private String mDeviceIp;
|
|
|
|
|
private Handler mHandler;
|
|
|
|
|
private Handler retryHandler = new Handler();
|
|
|
|
|
@NonNull
|
|
|
|
|
private com.xypower.mpremote.databinding.ActivityStreamBinding binding;
|
|
|
|
|
private String localIp;
|
|
|
|
@ -58,8 +56,18 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
|
|
|
|
|
private int rotation;
|
|
|
|
|
private int netCamera;
|
|
|
|
|
private int vendor;
|
|
|
|
|
private PlayerView playerView;
|
|
|
|
|
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 SurfaceView playerView;
|
|
|
|
|
private MediaPlayer mediaPlayer;
|
|
|
|
|
private LibVLC libVLC;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
@ -73,7 +81,7 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void initEvent() {
|
|
|
|
|
String url = "rtmp://" + mDeviceIp + "/live/0";
|
|
|
|
|
RTMP_URL = "rtmp://" + mDeviceIp + "/live/0";
|
|
|
|
|
|
|
|
|
|
String cmd = "am start -n com.xypower.mplive/com.xypower.mplive.MainActivity"
|
|
|
|
|
+ " --ei cameraId " + Integer.toString(cameraId)
|
|
|
|
@ -81,7 +89,7 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
|
|
|
|
|
+ " --ei netCamera " + Integer.toString(netCamera)
|
|
|
|
|
+ " --ei vendor " + Integer.toString(vendor)
|
|
|
|
|
+ " --ei autoStart 1"
|
|
|
|
|
+ " --es url \"" + url + "\"";
|
|
|
|
|
+ " --es url \"" + RTMP_URL + "\"";
|
|
|
|
|
Runnable runnable = new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
@ -89,10 +97,22 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
startStreaming(cmd, runnable);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void initHandler() {
|
|
|
|
|
mHandler = new Handler();
|
|
|
|
|
// mHandler = new Handler();
|
|
|
|
|
|
|
|
|
|
// 配置 VLC 参数
|
|
|
|
|
ArrayList<String> options = new ArrayList<>();
|
|
|
|
|
options.add("--aout=opensles"); // 优化音频延迟:ml-citation{ref="6" data="citationList"}
|
|
|
|
|
options.add("--audio-time-stretch");
|
|
|
|
|
|
|
|
|
|
libVLC = new LibVLC(this, options);
|
|
|
|
|
mediaPlayer = new MediaPlayer(libVLC);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void initIntent() {
|
|
|
|
@ -111,6 +131,25 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
|
|
|
|
|
binding.toolbar.back.setOnClickListener(this);
|
|
|
|
|
binding.toolbar.refresh.setOnClickListener(this);
|
|
|
|
|
playerView = binding.playerView;
|
|
|
|
|
SurfaceHolder holder = playerView.getHolder();
|
|
|
|
|
holder.addCallback(new SurfaceHolder.Callback() {
|
|
|
|
|
@Override
|
|
|
|
|
public void surfaceCreated(SurfaceHolder holder) {
|
|
|
|
|
Surface surface = holder.getSurface();
|
|
|
|
|
mediaPlayer.getVLCVout().setVideoSurface(surface,holder);
|
|
|
|
|
mediaPlayer.getVLCVout().attachViews(); // 绑定视图:ml-citation{ref="8" data="citationList"}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void startStreaming(final String cmd, final Runnable runnable) {
|
|
|
|
@ -124,7 +163,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) {
|
|
|
|
|
mHandler.postDelayed(new Runnable() {
|
|
|
|
|
retryHandler.postDelayed(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
Toast.makeText(StreamActivity.this, R.string.err_dev_failed_to_connect, Toast.LENGTH_LONG).show();
|
|
|
|
@ -147,7 +186,7 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
|
|
|
|
|
if (adbShellResponse != null) {
|
|
|
|
|
if (adbShellResponse.getExitCode() == 0) {
|
|
|
|
|
try {
|
|
|
|
|
Thread.sleep(100);
|
|
|
|
|
Thread.sleep(5000);
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
}
|
|
|
|
|
runOnUiThread(runnable);
|
|
|
|
@ -201,7 +240,7 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
|
|
|
|
|
th.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void initializePlayer() {
|
|
|
|
|
private void initializePlayer2() {
|
|
|
|
|
// // 创建重试策略
|
|
|
|
|
// DefaultLoadControl loadControl = new DefaultLoadControl.Builder()
|
|
|
|
|
// .setBufferDurationsMs(
|
|
|
|
@ -210,52 +249,154 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
|
|
|
|
|
// 500, // bufferForPlaybackMs
|
|
|
|
|
// 500 // bufferForPlaybackAfterRebufferMs
|
|
|
|
|
// ).build();
|
|
|
|
|
exoPlayer = new ExoPlayer.Builder(this).build();
|
|
|
|
|
playerView.setPlayer(exoPlayer);
|
|
|
|
|
exoPlayer.addListener(new Player.Listener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onPlayerError(PlaybackException error) {
|
|
|
|
|
Log.e(TAG, "播放错误: " + error.getMessage());
|
|
|
|
|
// player = new ExoPlayer.Builder(this).build();
|
|
|
|
|
// playerView.setPlayer(player);
|
|
|
|
|
// player.addListener(new Player.Listener() {
|
|
|
|
|
// @Override
|
|
|
|
|
// public void onPlayerError(PlaybackException error) {
|
|
|
|
|
// Log.e(TAG, "播放错误: " + error.getMessage());
|
|
|
|
|
// handlePlaybackError();
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// @Override
|
|
|
|
|
// public void onPlaybackStateChanged(int state) {
|
|
|
|
|
// if (state == Player.STATE_READY) {
|
|
|
|
|
// // 重置重试计数器当成功连接时
|
|
|
|
|
// retryCount = 0;
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// });
|
|
|
|
|
// startPlayback();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// private void handlePlaybackError2() {
|
|
|
|
|
// if (retryCount < MAX_RETRIES) {
|
|
|
|
|
// retryCount++;
|
|
|
|
|
// Log.d(TAG, "准备重试 (" + retryCount + "/" + MAX_RETRIES + ")...");
|
|
|
|
|
// mHandler.postDelayed(() -> {
|
|
|
|
|
// if (player != null) {
|
|
|
|
|
// startPlayback();
|
|
|
|
|
// }
|
|
|
|
|
// }, 3000);
|
|
|
|
|
// } else {
|
|
|
|
|
// Log.e(TAG, "达到最大重试次数,停止尝试");
|
|
|
|
|
// // 这里可以添加UI提示或执行其他错误处理
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
private void startPlayback2() {
|
|
|
|
|
if (player == null) return;
|
|
|
|
|
Log.d(TAG, "开始播放,重试次数: " + retryCount);
|
|
|
|
|
MediaItem mediaItem = MediaItem.fromUri("rtmp://" + mDeviceIp + "/live/0");
|
|
|
|
|
ProgressiveMediaSource videoSource = new ProgressiveMediaSource.Factory(new RtmpDataSource.Factory())
|
|
|
|
|
.createMediaSource(mediaItem);
|
|
|
|
|
player.setMediaSource(videoSource);
|
|
|
|
|
player.prepare();
|
|
|
|
|
player.setPlayWhenReady(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// @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) {
|
|
|
|
|
// isBuffering = false;
|
|
|
|
|
// retryHandler.removeCallbacks(bufferingTimeoutRunnable);
|
|
|
|
|
// retryCount = 0;
|
|
|
|
|
// currentRetryDelay = INITIAL_RETRY_DELAY_MS;
|
|
|
|
|
// Log.d(TAG, "播放器准备就绪");
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// });
|
|
|
|
|
// }
|
|
|
|
|
// startPlayback();
|
|
|
|
|
Media media = new Media(libVLC, Uri.parse(RTMP_URL));
|
|
|
|
|
mediaPlayer.setMedia(media);
|
|
|
|
|
mediaPlayer.play();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Runnable bufferingTimeoutRunnable = new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
if (isBuffering) {
|
|
|
|
|
Log.w(TAG, "缓冲超时,触发重连");
|
|
|
|
|
handlePlaybackError();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onPlaybackStateChanged(int state) {
|
|
|
|
|
if (state == Player.STATE_READY) {
|
|
|
|
|
// 重置重试计数器当成功连接时
|
|
|
|
|
retryCount = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
startPlayback();
|
|
|
|
|
private void startPlayback() {
|
|
|
|
|
if (player == null) return;
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "开始播放,重试次数: " + retryCount);
|
|
|
|
|
// MediaItem mediaItem = MediaItem.fromUri(Uri.parse(RTMP_URL));
|
|
|
|
|
|
|
|
|
|
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 + "/" + MAX_RETRIES + ")...");
|
|
|
|
|
mHandler.postDelayed(() -> {
|
|
|
|
|
if (exoPlayer != null) {
|
|
|
|
|
startPlayback();
|
|
|
|
|
}
|
|
|
|
|
}, 3000);
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "达到最大重试次数,停止尝试");
|
|
|
|
|
// 这里可以添加UI提示或执行其他错误处理
|
|
|
|
|
Log.d(TAG, "准备重试 (" + retryCount + "), 延迟: " + currentRetryDelay + "ms");
|
|
|
|
|
|
|
|
|
|
retryHandler.postDelayed(() -> {
|
|
|
|
|
initializePlayer();
|
|
|
|
|
// 增加下次重试的延迟时间(使用退避算法)
|
|
|
|
|
currentRetryDelay = Math.min((long)(currentRetryDelay * RETRY_BACKOFF_MULTIPLIER), MAX_RETRY_DELAY_MS);
|
|
|
|
|
}, currentRetryDelay);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void startPlayback() {
|
|
|
|
|
if (exoPlayer == null) return;
|
|
|
|
|
Log.d(TAG, "开始播放,重试次数: " + retryCount);
|
|
|
|
|
MediaItem mediaItem = MediaItem.fromUri("rtmp://" + mDeviceIp + "/live/0");
|
|
|
|
|
ProgressiveMediaSource videoSource = new ProgressiveMediaSource.Factory(new RtmpDataSource.Factory())
|
|
|
|
|
.createMediaSource(mediaItem);
|
|
|
|
|
exoPlayer.setMediaSource(videoSource);
|
|
|
|
|
exoPlayer.prepare();
|
|
|
|
|
exoPlayer.setPlayWhenReady(true);
|
|
|
|
|
private void releasePlayerInternal() {
|
|
|
|
|
if (player != null) {
|
|
|
|
|
player.release();
|
|
|
|
|
player = null;
|
|
|
|
|
isBuffering = false;
|
|
|
|
|
retryHandler.removeCallbacks(bufferingTimeoutRunnable);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onDestroy() {
|
|
|
|
|
super.onDestroy();
|
|
|
|
@ -281,9 +422,9 @@ public class StreamActivity extends AppCompatActivity implements View.OnClickLis
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void releasePlayer() {
|
|
|
|
|
if (exoPlayer != null) {
|
|
|
|
|
exoPlayer.release();
|
|
|
|
|
exoPlayer = null;
|
|
|
|
|
if (player != null) {
|
|
|
|
|
player.release();
|
|
|
|
|
player = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|