Compare commits

...

15 Commits

@ -4,12 +4,13 @@ plugins {
def AppMajorVersion = 1
def AppMinorVersion = 0
def AppBuildNumber = 15
def AppBuildNumber = 17
def AppVersionName = AppMajorVersion + "." + AppMinorVersion + "." + AppBuildNumber
def AppVersionCode = AppMajorVersion * 100000 + AppMinorVersion * 1000 + AppBuildNumber
android {
namespace "com.xypower.mpremote"
compileSdk 33
defaultConfig {
@ -78,19 +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'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

@ -18,4 +18,11 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile
-keep class 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"

@ -37,7 +37,6 @@ public class AdbManager {
}
public static Dadb getAdb(String ip, int port) {
synchronized (mLocker) {
if (mAdbs.containsKey(ip)) {
InternalAdb adb = mAdbs.get(ip);
@ -57,15 +56,11 @@ public class AdbManager {
}
adb = null;
}
InternalAdb adb = new InternalAdb();
adb.ip = ip;
adb.port = port;
adb.adb = Dadb.create(ip, port, mAdbKeyPair);
mAdbs.put(ip, adb);
return adb.adb;
}
}

@ -0,0 +1,37 @@
package com.xypower.mpremote;
public class Constants {
public static final String PACKAGE_NAME_MP = "com.xypower.mpapp";
public static final String PACKAGE_NAME_MPMASTER = "com.xypower.mpmaster";
public static final String PACKAGE_NAME_MPREMOTE = "com.xypower.mpremote";
public static final String REMOTE_PATH_ROOT = "/sdcard/" + PACKAGE_NAME_MP + "/";
public static final String REMOTE_PATH_DATA = REMOTE_PATH_ROOT + "data/";
public static final String REMOTE_PATH_DATA_CHANNELS = REMOTE_PATH_ROOT + "data/channels/";
public static final String REMOTE_PATH_PHOTOS = REMOTE_PATH_ROOT + "photos/";
public static final String REMOTE_PATH_TMP = REMOTE_PATH_ROOT + "tmp/";
public static final String REMOTE_PATH_APP = REMOTE_PATH_ROOT + "data/App.json";
public static final String LOCAL_PATH_REMOTE = "/sdcard/" + PACKAGE_NAME_MPREMOTE + "/" + "tmp/App.json";
public static final String KEY_APP_BV = "app.bv";
public static final String KEY_APP_BCV = "app.bcv";
public static final String KEY_RO_SERIALNO = "ro.serialno";
public static final String KEY_RO_CUSTOM_OTA_VERSION = "ro.custom.ota.version";
public static final String KEY_RO_MODEMS_MAX_COUNT = "telephony.active_modems.max_count";
// ro.telephony.sim.count
public static final String KEY_RO_SIGNAL_STRNGTH_PREFIX = "vendor.ril.nw.signalstrength.lte.";
public static final String KEY_RO_ICCID_PREFIX = "persist.vendor.radio.cfu.iccid.";
public static final String KEY_GSM_VENDOR_NAME = "gsm.operator.alpha";
public static final String KEY_APP_MP_VERSION = "mpapp.version";
public static final String KEY_APP_MPMASTER_VERSION = "mpmaster.version";
public static final String KEY_PREFIX_GLOBAL_SETTING = "settings.global.";
public static final String KEY_IMEI = KEY_PREFIX_GLOBAL_SETTING + "dev_imei";
public static final String KEY_IMEI2 = KEY_PREFIX_GLOBAL_SETTING + "dev_imei2";
}

@ -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;
// }
//}

File diff suppressed because it is too large Load Diff

@ -1,244 +1,127 @@
package com.xypower.mpremote;
import android.annotation.SuppressLint;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import android.content.Context;
import android.content.ContentUris;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import com.xypower.mpremote.adapter.ImageItemAdapter;
import com.xypower.mpremote.databinding.ActivityImageBinding;
import com.xypower.mpremote.utils.AppUtils;
import com.xypower.mpremote.utils.FileUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*/
public class ImageActivity extends AppCompatActivity {
/**
* Whether or not the system UI should be auto-hidden after
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
*/
private static final boolean AUTO_HIDE = true;
/**
* If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
* user interaction before hiding the system UI.
*/
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
/**
* Some older devices needs a small delay between UI widget updates
* and a change of the status and navigation bar.
*/
private static final int UI_ANIMATION_DELAY = 300;
private final Handler mHideHandler = new Handler(Looper.myLooper());
public class ImageActivity extends AppCompatActivity implements View.OnClickListener {
private String mSerialNo = "";
private List<File> mImageFiles = new ArrayList<>();
private ImagesAdaper mAdapter;
private List<Map<String, Object>> mItems = new ArrayList<Map<String, Object>>();
/**
* Touch listener to use for in-layout UI controls to delay hiding the
* system UI. This is to prevent the jarring behavior of controls going away
* while interacting with activity UI.
*/
private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
ImageActivity.this.finish();
break;
default:
break;
}
return false;
}
};
private ImageItemAdapter mAdapter;
private ActivityImageBinding binding;
private String path;//图片地址
private String cmdid;
private String photodirpath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
binding = ActivityImageBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
initIntent();
initView();
initData();
}
binding.imageView.setImageDrawable(null);
binding.imageView.setClickable(true);
binding.imageView.setOnTouchListener(mDelayHideTouchListener);
binding.imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ImageActivity.this.finish();
}
});
private void initIntent() {
Intent intent = getIntent();
String path = intent.getStringExtra("path");
if (path != null) {
loadImage(path);
}
path = intent.getStringExtra("path");
photodirpath = intent.getStringExtra("photodirpath");
cmdid = intent.getStringExtra("cmdid");
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
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);
mAdapter = new ImageItemAdapter();
// mAdapter.setOnClickListener(this);
binding.recyclerView.setAdapter(mAdapter);
binding.recyclerView.setHasFixedSize(true);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.imageView.requestLayout();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return false;
default:
return super.onOptionsItemSelected(item);
}
private void initData() {
// getPicList();
loadPhotos();
}
private boolean loadImage(String path) {
private void loadPhotos() {
List<String> imageFiles = FileUtils.getImageFiles(photodirpath);
mAdapter.setItemList(imageFiles);
}
binding.imageView.setImageDrawable(null);
//获取文件夹下所有的照片
private void getPicList() {
// 获取应用专属目录的相对路径
// String relativePath = Environment.DIRECTORY_PICTURES + "/" + Constants.PACKAGE_NAME_MPREMOTE + "/";
String relativePath = photodirpath;
File file = new File(path);
if (!file.exists()) {
return false;
}
Drawable drawable = loadDrawable(path);
if (drawable != null) {
binding.imageView.setImageDrawable(drawable);
}
String[] projection = {MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME};
return drawable != null;
}
String selection = MediaStore.Images.Media.RELATIVE_PATH + " = ?";
String[] selectionArgs = new String[]{relativePath};
private Drawable loadDrawable(String file) {
if (file == null || file.isEmpty()) {
return null;
}
Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, null);
List<String> imageList = new ArrayList<>();
if (cursor != null) {
int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
int nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME);
while (cursor.moveToNext()) {
long id = cursor.getLong(idColumn);
String name = cursor.getString(nameColumn);
Uri contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
Drawable drawable = null;
try {
Bitmap bitmap = BitmapFactory.decodeFile(file);
drawable = new BitmapDrawable(getResources(), bitmap);
} catch (Exception e) {
e.printStackTrace();
imageList.add(name);
}
cursor.close();
}
return drawable;
Log.e("dfsdaf", imageList.toString());
}
protected void loadAllImages() {
File localDevicePath = new File(getFilesDir(), "Photos" + File.separator + mSerialNo);
if (!localDevicePath.exists()) {
localDevicePath.mkdirs();
}
// binding.imagesView.
}
private List<Map<String, Object>> getData() {
mItems.clear();
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String ssid = "";
if (wifiInfo != null) {
ssid = wifiInfo.getSSID();
if (!TextUtils.isEmpty(ssid)) {
if (ssid.startsWith("\"") && ssid.endsWith("\"")) {
ssid = ssid.substring(1, ssid.length() - 1);
}
}
}
for (File file : mImageFiles) {
Map map = new HashMap<String, Object>();
// Bitmap bm =
map.put("img", null);
// if (scanR)
map.put("text", file.getName());
mItems.add(map);
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.back:
finish();
break;
case R.id.refresh:
loadPhotos();
break;
}
return mItems;
}
private void refreshListView() {
mAdapter = new ImagesAdaper(this, getData(), R.layout.list_item, new String[] { "img", "text" },
new int[] { R.id.id_img, R.id.id_text });
binding.imagesView.setAdapter(mAdapter);
}
public class ImagesAdaper extends SimpleAdapter {
public ImagesAdaper(Context context, List<Map<String, Object>> items, int resource, String[] from, int[] to) {
super(context, items, resource, from, to);
}
public View getView(int position, View convertView, ViewGroup parent){
convertView = super.getView(position, convertView, parent);
TextView textView = (TextView)convertView.findViewById(R.id.id_channel);
File file = mImageFiles.get(position);
// if (scanResult.)
String text = (String)mItems.get(position).get("text");
if (text.startsWith("XY") || text.startsWith("xy")) {
textView.setTextColor(Color.RED);
textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
} else {
textView.setTextColor(Color.BLACK);
textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
}
return convertView;
}
}
}

@ -1,185 +1,98 @@
package com.xypower.mpremote;
import androidx.annotation.NonNull;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.DhcpInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkSpecifier;
import android.net.wifi.WifiNetworkSuggestion;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import com.xypower.mpremote.databinding.ActivityMainBinding;
import com.xypower.mpremote.utils.AppUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
// Used to load the 'mpremote' library on application startup.
private static final String TAG = "ADB";
public static final int RSSI_LEVELS = 5;
private static final String DEFAULT_PRE_SHARED_KEY = SettingsActivity.DEFAULT_PRE_SHARED_KEY;
private static final int REQUEST_CODE_PERMISSIONS = 1025;
private static final int REQUEST_CODE_SETTINGS = SettingsActivity.REQUEST_CODE_SETTINGS;
private static final String WIFI_IP_PREFIX = "192.168.";
private ActivityMainBinding binding;
private Handler mHandler;
private List<ScanResult> mScanResults = new ArrayList<>();
private ScanResult mCurrentScanResult;
private String mPassword = DEFAULT_PRE_SHARED_KEY;
private boolean mUsingGatewayIp = true;
private String mAssignedIp = "";
private WifiAdaper mAdapter;
private List<Map<String, Object>> mItems = new ArrayList<Map<String, Object>>();
private ConnectivityManager mConnectivityManager;
private NetworkConnectChangedReceiver mNetworkConnectChangedReceiver;
private ConnectivityManager.NetworkCallback mNetworkCallback;
public class WifiAdaper extends SimpleAdapter{
public WifiAdaper(Context context, List<Map<String, Object>> items, int resource, String[] from, int[] to) {
super(context, items, resource, from, to);
}
public View getView(int position, View convertView, ViewGroup parent){
convertView = super.getView(position, convertView, parent);
TextView textView = (TextView)convertView.findViewById(R.id.id_text);
ScanResult scanResult = mScanResults.get(position);
// if (scanResult.)
String text = (String)mItems.get(position).get("text");
if (text.startsWith("XY") || text.startsWith("xy")) {
textView.setTextColor(Color.RED);
textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
} else {
textView.setTextColor(Color.BLACK);
textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
}
return convertView;
}
}
private WifiManager wifiManager;
private ActivityResultLauncher<Intent> wifiLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);//获取wifi管理器
initView();//初始化页面显示
initActivityResult();//初始化页面回调
initEvent();//初始化事件
}
loadSettings();
mHandler = new Handler();
AdbManager.initManager(this);
mConnectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
binding.listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (!wifiManager.isWifiEnabled()) {
wifiManager.setWifiEnabled(true);
}
mCurrentScanResult = mScanResults.get(position);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String s1 = normalizeSSID(mCurrentScanResult.SSID);
String s2 = normalizeSSID(wifiInfo.getSSID());
if (TextUtils.equals(s1, s2)) {
openDeviceActivity();
} else {
startActivity(new Intent(android.provider.Settings.ACTION_WIFI_SETTINGS));
}
// connectWifi(mCurrentScanResult.SSID, mCurrentScanResult.BSSID, mPassword);
}
});
private void initEvent() {
AdbManager.initManager(this);//初始化Adb
if (applyForPermissions()) {
scanWifi();
getConnectWiFi();//获取已连接wifi
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mConnectivityManager != null && mNetworkCallback != null) {
mConnectivityManager.bindProcessToNetwork(null);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
private void initView() {
// 设置完全透明状态栏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
);
getWindow().setStatusBarColor(Color.TRANSPARENT);
}
String versionName = AppUtils.getAppNameWithVersion(this);
binding.toolbar.title.setText(versionName);
binding.toolbar.back.setVisibility(View.INVISIBLE);
binding.selectWifi.setOnClickListener(this);
binding.control.setOnClickListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main_activity_actions, menu);
return true;
private void initActivityResult() {
wifiLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
getConnectWiFi();
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
//当点击不同的menu item 是执行不同的操作
switch (id) {
case R.id.action_refresh:
scanWifi();
break;
case R.id.action_settings: {
Intent intent = new Intent(getApplicationContext(), SettingsActivity.class);
startActivityForResult(intent, REQUEST_CODE_SETTINGS);
}
break;
default:
break;
}
return super.onOptionsItemSelected(item);
protected void onDestroy() {
super.onDestroy();
}
@Override
@ -188,13 +101,13 @@ public class MainActivity extends AppCompatActivity {
try {
ArrayList<String> requestList = new ArrayList<>();//允许询问列表
ArrayList<String> banList = new ArrayList<>();//禁止列表
for(int i = 0; i < permissions.length; i++) {
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG,"【"+permissions[i]+"】权限授权成功");
Log.i(TAG, "【" + permissions[i] + "】权限授权成功");
} else {
//判断是否允许重新申请该权限
boolean nRet = ActivityCompat.shouldShowRequestPermissionRationale(this,permissions[i]);
Log.i(TAG,"shouldShowRequestPermissionRationale nRet="+nRet);
boolean nRet = ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i]);
Log.i(TAG, "shouldShowRequestPermissionRationale nRet=" + nRet);
if (nRet) {//允许重新申请
requestList.add(permissions[i]);
} else {//禁止申请
@ -202,18 +115,15 @@ public class MainActivity extends AppCompatActivity {
}
}
}
//优先对禁止列表进行判断
if (banList.size() > 0) {
//告知该权限作用,要求手动授予权限
showFinishedDialog();
}
else if (requestList.size() > 0) {
} else if (requestList.size() > 0) {
//告知权限的作用,并重新申请
showTipDialog(requestList);
} else {
// showToast("权限授权成功");
scanWifi();
}
} catch (Exception e) {
e.printStackTrace();
@ -236,23 +146,14 @@ public class MainActivity extends AppCompatActivity {
dialog.show();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_SETTINGS) {
loadSettings();
}
}
public void showTipDialog(ArrayList<String> pmList){
public void showTipDialog(ArrayList<String> pmList) {
AlertDialog dialog = new AlertDialog.Builder(getApplicationContext())
.setTitle("提示")
.setMessage("【"+pmList.toString()+"】权限为应用必要权限,请授权")
.setMessage("【" + pmList.toString() + "】权限为应用必要权限,请授权")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String[] sList=pmList.toArray(new String[0]);
String[] sList = pmList.toArray(new String[0]);
//重新申请该权限
ActivityCompat.requestPermissions(MainActivity.this, sList, REQUEST_CODE_PERMISSIONS);
}
@ -261,251 +162,37 @@ public class MainActivity extends AppCompatActivity {
dialog.show();
}
protected void scanWifi() {
mScanResults.clear();
(new Thread(new Runnable() {
@Override
public void run() {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
wifiManager.startScan();
List<ScanResult> scanResults = wifiManager.getScanResults();
Map<String, String> ssids = new HashMap<>();
for (ScanResult scanResult : scanResults) {
if (TextUtils.isEmpty(scanResult.SSID)) {
continue;
}
if (ssids.containsKey(scanResult.SSID)) {
continue;
}
ssids.put(scanResult.SSID, scanResult.SSID);
mScanResults.add(scanResult);
}
// mAdapter = new ArrayAdapter<>()
mHandler.post(new Runnable() {
@Override
public void run() {
refreshListView();
}
});
}
})).start();
}
private void connectWifi(String ssid, String bssid, String pwd) {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
// Android 10
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
WifiNetworkSuggestion suggestion =
new WifiNetworkSuggestion.Builder()
.setSsid(ssid)
.setWpa2Passphrase((pwd))
.setIsAppInteractionRequired(true)
.build();
List<WifiNetworkSuggestion> suggestionsList = new ArrayList<>();
suggestionsList.add(suggestion);
wifiManager.removeNetworkSuggestions(suggestionsList);
int status = wifiManager.addNetworkSuggestions(suggestionsList);
// step2
if ((status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) || (status == WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE)) {
WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier.Builder()
.setSsid(ssid)
.setWpa2Passphrase(pwd)
.build();
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.setNetworkSpecifier(wifiNetworkSpecifier)
.build();
mNetworkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(@NonNull Network network) {
super.onAvailable(network);
NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String s1 = normalizeSSID(wifiInfo.getSSID());
String s2 = normalizeSSID(mCurrentScanResult.SSID);
if (TextUtils.equals(s1, s2)) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// requires android.permission.INTERNET
mConnectivityManager.bindProcessToNetwork(network);
openDeviceActivity();
}
});
}
}
@Override
public void onUnavailable() {
super.onUnavailable();
runOnUiThread(new Runnable() {
@Override
public void run() {
mConnectivityManager.bindProcessToNetwork(null);
}
});
}
};
mConnectivityManager.requestNetwork(request, mNetworkCallback);
} else {
}
} else {
WifiConfiguration wifiConfiguration = new WifiConfiguration();
wifiConfiguration.SSID = "\"" + ssid + "\"";
wifiConfiguration.BSSID = "\"" + bssid + "\"";
wifiConfiguration.preSharedKey = "\"" + pwd + "\"";
int nerworkId = wifiManager.addNetwork(wifiConfiguration);
try {
wifiManager.disconnect();
} catch (Exception ex) {
ex.printStackTrace();
}
/*
if (mNetworkConnectChangedReceiver != null) {
unregisterReceiver(mNetworkConnectChangedReceiver);
mNetworkConnectChangedReceiver = null;
registerNetworkConnectChangeReceiver();
}
*/
wifiManager.enableNetwork(nerworkId, true);
wifiManager.reconnect();
//页面按钮的点击事件
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.selectWifi:
openWifiActivity();
break;
case R.id.control:
openDeviceActivity();
break;
}
}
private void refreshListView() {
mAdapter = new WifiAdaper(this, getData(), R.layout.list_item, new String[] { "img", "text" },
new int[] { R.id.id_img, R.id.id_text });
binding.listView.setAdapter(mAdapter);
}
private List<Map<String, Object>> getData() {
mItems.clear();
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String ssid = "";
if (wifiInfo != null) {
ssid = wifiInfo.getSSID();
if (!TextUtils.isEmpty(ssid)) {
if (ssid.startsWith("\"") && ssid.endsWith("\"")) {
ssid = ssid.substring(1, ssid.length() - 1);
}
}
}
for (ScanResult scanResult : mScanResults) {
Map map = new HashMap<String, Object>();
int signalLevel = WifiManager.calculateSignalLevel(scanResult.level, RSSI_LEVELS);
int drawableId = R.drawable.ic_wifi_signal_0;
boolean connected = TextUtils.equals(ssid, scanResult.SSID);
switch (signalLevel) {
case 1:
drawableId = connected ? R.drawable.ic_wifi_signal_1_green : R.drawable.ic_wifi_signal_1;
break;
case 2:
drawableId = connected ? R.drawable.ic_wifi_signal_2_green : R.drawable.ic_wifi_signal_2;
break;
case 3:
drawableId = connected ? R.drawable.ic_wifi_signal_3_green : R.drawable.ic_wifi_signal_3;
break;
case 4:
drawableId = connected ? R.drawable.ic_wifi_signal_4_green : R.drawable.ic_wifi_signal_4;
break;
default:
drawableId = connected ? R.drawable.ic_wifi_signal_0_green : R.drawable.ic_wifi_signal_0;
break;
//获取已连接WIFI
private void getConnectWiFi() {
if (wifiManager != null) {
if (!wifiManager.isWifiEnabled()) {
wifiManager.setWifiEnabled(true);
}
map.put("img", drawableId);
// if (scanR)
map.put("text", scanResult.SSID);
mItems.add(map);
}
return mItems;
}
class NetworkConnectChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
// 这个监听wifi的连接状态即是否连上了一个有效无线路由当上边广播的状态是WifiManager.WIFI_STATE_DISABLING和WIFI_STATE_DISABLED的时候根本不会接到这个广播。
// 在上边广播接到广播是WifiManager.WIFI_STATE_ENABLED状态的同时也会接到这个广播当然刚打开wifi肯定还没有连接到有效的无线
if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
Parcelable parcelableExtra = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
String bssid = intent.getStringExtra(WifiManager.EXTRA_BSSID);
if (null == parcelableExtra) {
return;
}
if (mCurrentScanResult != null) {
return;
}
NetworkInfo networkInfo = (NetworkInfo) parcelableExtra;
NetworkInfo.State state = networkInfo.getState();
// Log.e("NetWork Sate Change:"+state+" connectedBssid:" + connectedBssid);
if(state == NetworkInfo.State.CONNECTED) {
String ssid = wifiInfo.getSSID();
String addSSID = mCurrentScanResult.SSID;
if (!(mCurrentScanResult.SSID.startsWith("\"") && mCurrentScanResult.SSID.endsWith("\""))) {
addSSID = "\"" + addSSID + "\"";
}
// Log.i(ssid + "***>" + mCurrentScanResult.SSID);
// Log.i("总共耗时:"+((System.currentTimeMillis()-start)/1000.0));
if (ssid.equals(addSSID)) {
unregisterReceiver(mNetworkConnectChangedReceiver);
mNetworkConnectChangedReceiver = null;
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo != null) {
String ssid = wifiInfo.getSSID();
if (!TextUtils.isEmpty(ssid)) {
if (ssid.startsWith("\"") && ssid.endsWith("\"")) {
ssid = ssid.substring(1, ssid.length() - 1);
}
}
binding.selectWifi.setText("已连接WIFI" + ssid);
}
}
}
private void registerNetworkConnectChangeReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
mNetworkConnectChangedReceiver = new NetworkConnectChangedReceiver();
registerReceiver(mNetworkConnectChangedReceiver, filter);
}
public boolean applyForPermissions() {
String[] PM_MULTIPLE = {
@ -520,9 +207,9 @@ public class MainActivity extends AppCompatActivity {
try {
ArrayList<String> pmList = new ArrayList<>();
//获取当前未授权的权限列表
for(String permission:PM_MULTIPLE) {
int nRet = ContextCompat.checkSelfPermission(this,permission);
if(nRet != PackageManager.PERMISSION_GRANTED) {
for (String permission : PM_MULTIPLE) {
int nRet = ContextCompat.checkSelfPermission(this, permission);
if (nRet != PackageManager.PERMISSION_GRANTED) {
pmList.add(permission);
}
}
@ -530,62 +217,39 @@ public class MainActivity extends AppCompatActivity {
if (pmList.size() > 0) {
// Log.i(TAG,"进行权限申请...");
String[] sList = pmList.toArray(new String[0]);
ActivityCompat.requestPermissions(this, sList,REQUEST_CODE_PERMISSIONS);
ActivityCompat.requestPermissions(this, sList, REQUEST_CODE_PERMISSIONS);
return false;
}
} catch(Exception e){
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
protected void loadSettings() {
SharedPreferences preferences = getSharedPreferences("mpremote", MODE_PRIVATE);
mPassword = preferences.getString("password", DEFAULT_PRE_SHARED_KEY);
mUsingGatewayIp = preferences.getBoolean("usingGateway", true);
mAssignedIp = preferences.getString("assignedIp", "");
}
//打开设备控制页面
protected void openDeviceActivity() {
String ipAddressByWifi = null;
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager != null) {
ipAddressByWifi = Formatter.formatIpAddress(wifiManager.getDhcpInfo().ipAddress);
if (!wifiManager.isWifiEnabled()) {
wifiManager.setWifiEnabled(true);
}
DhcpInfo dhcpInfo = wifiManager.getDhcpInfo();
ipAddressByWifi = Formatter.formatIpAddress(dhcpInfo.ipAddress);
if (ipAddressByWifi.contains(WIFI_IP_PREFIX)) {
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String localIP = Formatter.formatIpAddress(wifiInfo.getIpAddress());
String deviceIP = Formatter.formatIpAddress(wifiManager.getDhcpInfo().gateway);
if (!mUsingGatewayIp) {
deviceIP = mAssignedIp;
}
String deviceIP = Formatter.formatIpAddress(dhcpInfo.gateway);
Intent intent = new Intent(MainActivity.this, DeviceActivity.class);
// intent1.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("deviceIp", deviceIP);
intent.putExtra("localIp", localIP);
intent.putExtra("SSID", mCurrentScanResult.SSID);
startActivity(intent);
}
}
}
private String normalizeSSID(String ssid) {
if (TextUtils.isEmpty(ssid)) {
return ssid;
}
if (!(ssid.startsWith("\"") && ssid.endsWith("\""))) {
return "\"" + ssid + "\"";
}
return ssid;
//打开WIFI连接页面
private void openWifiActivity() {
Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
wifiLauncher.launch(intent);
}
}

@ -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);
// }
//}

@ -12,7 +12,6 @@ import com.xypower.mpremote.databinding.ActivitySettingsBinding;
public class SettingsActivity extends AppCompatActivity {
public static final int REQUEST_CODE_SETTINGS = 13;
public static final String DEFAULT_PRE_SHARED_KEY = "12345678";
private ActivitySettingsBinding binding;
@ -62,7 +61,7 @@ public class SettingsActivity extends AppCompatActivity {
switch (item.getItemId()) {
case android.R.id.home:
saveSettings();
setResult(0);
setResult(RESULT_OK);
finish();
return false;
default:

@ -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,106 +1,162 @@
package com.xypower.mpremote;
import androidx.annotation.OptIn;
import androidx.appcompat.app.ActionBar;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.rtmp.RtmpDataSource;
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.Environment;
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.LinearLayout;
import android.widget.Toast;
import com.xypower.common.FilesUtils;
import com.xypower.mpremote.databinding.ActivityStreamBinding;
import com.xypower.mpremote.utils.AdbUtils;
import com.xypower.mpremote.zlmediakit.ZLMediaKit;
import com.xypower.mpremote.utils.AlertDialogUtils;
import com.xypower.mpremote.utils.CountdownAlertDialog;
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 org.videolan.libvlc.interfaces.IVLCVout;
import dadb.AdbKeyPair;
import dadb.AdbShellResponse;
import dadb.Dadb;
import java.util.ArrayList;
public class StreamActivity extends AppCompatActivity {
import android.Manifest;
import android.content.pm.PackageManager;
import android.view.TextureView;
// private PlayerView playerView;
private static final String TAG = "STRM";
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
private ExoPlayer exoPlayer;
import dadb.AdbShellResponse;
import dadb.Dadb;
public class StreamActivity extends AppCompatActivity implements IVLCVout.Callback, TextureView.SurfaceTextureListener, View.OnClickListener {
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 mDeviceIp;
private Handler mHandler;
private int cameraId;
private int rotation;
private int netCamera;
private int vendor;
private int selfTestingTime;
private int channel;
private AlertDialog alertDialog;
private LinearLayout back;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_stream);
binding = ActivityStreamBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
initIntent();
initView();
initHandler();
initEvent();
// 检查并请求权限
if (!checkPermissions()) {
requestPermissions();
}
}
mHandler = new Handler();
private void initHandler() {
handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1:
AlertDialogUtils.dismiss(alertDialog);
break;
case 2:
CountdownAlertDialog.dismiss();
break;
}
}
};
}
ActionBar actionBar = getSupportActionBar();
// 显示返回按钮
actionBar.setDisplayHomeAsUpEnabled(true);
// 去掉logo图标
actionBar.setDisplayShowHomeEnabled(false);
private void initView() {
textureView = binding.textureView;
textureView.setSurfaceTextureListener(this);
back = binding.toolbar.back;
back.setOnClickListener(this);
}
private void initIntent() {
Intent intent = getIntent();
mDeviceIp = intent.getStringExtra("deviceIp");
String localIp = intent.getStringExtra("localIp");
int cameraId = intent.getIntExtra("cameraId", 0);
int channel = intent.getIntExtra("channel", 1);
int rotation = intent.getIntExtra("rotation", -1);
int netCamera = intent.getIntExtra("netCamera", 0);
int vendor = intent.getIntExtra("vendor", 0);
actionBar.setTitle(getResources().getString(R.string.channel) + Integer.toString(channel));
String 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 \"" + url + "\"";
Runnable runnable = new Runnable() {
@Override
public void run() {
initializePlayer();
cameraId = intent.getIntExtra("cameraId", 0);
channel = intent.getIntExtra("channel", 1);
rotation = intent.getIntExtra("rotation", -1);
netCamera = intent.getIntExtra("netCamera", 0);
vendor = intent.getIntExtra("vendor", 0);
selfTestingTime = intent.getIntExtra("selfTestingTime", 0);
}
private void initEvent() {
if (netCamera == 0) {
alertDialog = AlertDialogUtils.show(this, "视频加载中");
pushUrl = "rtmp://127.0.0.1/live/" + channel;
} else {
if (selfTestingTime > 0) {
CountdownAlertDialog.show(this, selfTestingTime, new CountdownAlertDialog.OnCountdownFinishedListener() {
@Override
public void onCountdownFinished() {
}
});
}
};
pushUrl = "rtsp://127.0.0.1:8554/live/" + channel;
}
startStreaming(cmd, runnable);
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();
}
};
startStreaming(cmd, runnable);
}
}
private void startStreaming(final String cmd, final Runnable runnable) {
Log.d(TAG, cmd);
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) {
mHandler.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();
@ -108,68 +164,62 @@ public class StreamActivity extends AppCompatActivity {
}, 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(1000);
} catch (Exception ex) {
if (netCamera == 0) {
try {
Thread.sleep(150);
} catch (Exception ex) {
}
} else {
try {
Thread.sleep(40000);
} catch (Exception ex) {
}
}
runOnUiThread(runnable);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
th.start();
}
private void stopStreaming() {
// String cmd = "am start -n com.xypower.mplive/com.xypower.mplive.MainActivity --ei autoClose 1";
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) {
}
@ -180,74 +230,256 @@ public class StreamActivity extends AppCompatActivity {
}
});
th.start();
}
private void initializePlayer() {
PlayerView playerView = findViewById(R.id.playerView);
exoPlayer = new ExoPlayer.Builder(this).build();
playerView.setPlayer(exoPlayer);
MediaItem mediaItem = MediaItem.fromUri("rtmp://" + mDeviceIp + "/live/0");
ProgressiveMediaSource videoSource = new ProgressiveMediaSource.Factory(new RtmpDataSource.Factory())
.createMediaSource(mediaItem);
private boolean checkPermissions() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET) == PackageManager.PERMISSION_GRANTED;
}
exoPlayer.setMediaSource(videoSource);
exoPlayer.prepare();
exoPlayer.play();
private void requestPermissions() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.INTERNET},
PERMISSION_REQUEST_CODE);
}
@Override
protected void onDestroy() {
super.onDestroy();
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) {
} else {
Toast.makeText(this, "需要存储权限才能播放视频", Toast.LENGTH_SHORT).show();
finish();
}
}
}
stopStreaming();
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();
}
}
@OptIn(markerClass = UnstableApi.class) @Override
protected void onPause() {
super.onPause();
if (Util.SDK_INT < 24) {
releasePlayer();
private void attemptReconnect() {
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
reconnectAttempts++;
Log.d(TAG, "尝试重连 (第" + reconnectAttempts + "次)");
handler.postDelayed(() -> {
releasePlayer();
initializePlayer();
}, RECONNECT_DELAY);
} else {
Log.e(TAG, "达到最大重连次数,停止尝试");
handler.post(() -> Toast.makeText(StreamActivity.this, "无法连接到直播流,已达到最大重试次数", Toast.LENGTH_LONG).show());
}
}
@OptIn(markerClass = UnstableApi.class) @Override
protected void onStop() {
super.onStop();
if (Util.SDK_INT >= 24) {
releasePlayer();
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 void releasePlayer() {
exoPlayer.release();
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.back:
finish();
break;
}
}
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
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);
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Log.d(TAG, "SurfaceTexture 可用");
surfaceTexture = surface;
isSurfaceReady = true;
if (isPlayerReady) {
loadMedia();
}
}
@Override
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); // 自动缩放
}
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
Log.d(TAG, "SurfaceTexture 销毁");
releasePlayer();
isSurfaceReady = false;
return true;
}
@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 onSurfaceTextureUpdated(SurfaceTexture surface) {
// 每次 SurfaceTexture 更新时调用
}
@Override
public void onSurfacesCreated(IVLCVout vout) {
Log.d(TAG, "VLC 表面创建完成");
}
@Override
public void onSurfacesDestroyed(IVLCVout vout) {
Log.d(TAG, "VLC 表面销毁");
}
@Override
protected void onResume() {
super.onResume();
if (isPlayerReady && !mediaPlayer.isPlaying() && isSurfaceReady) {
loadMedia();
}
}
@Override
protected void onPause() {
super.onPause();
if (mediaPlayer != null) {
mediaPlayer.pause();
}
}
@Override
protected void onStop() {
super.onStop();
if (mediaPlayer != null) {
mediaPlayer.stop();
}
}
return super.onOptionsItemSelected(item);
@Override
protected void onDestroy() {
super.onDestroy();
CountdownAlertDialog.dismiss();
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

@ -0,0 +1,45 @@
package com.xypower.mpremote;
import android.net.wifi.WifiNetworkSuggestion;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.content.Context;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import java.util.Collections;
public class WifiConnector {
private final Context context;
private final WifiManager wifiManager;
public WifiConnector(Context context) {
this.context = context;
this.wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
@RequiresApi(api = Build.VERSION_CODES.Q)
public void suggestWifiNetwork(String ssid, String password) {
if (wifiManager == null) {
Toast.makeText(context, "Wi-Fi 服务不可用", Toast.LENGTH_SHORT).show();
return;
}
// 创建 Wi-Fi 网络建议
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(ssid) // SSID
.setWpa2Passphrase(password) // 密码
.setIsAppInteractionRequired(true) // 需要用户交互
.build();
// 提交网络建议
int status = wifiManager.addNetworkSuggestions(Collections.singletonList(suggestion));
if (status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
Toast.makeText(context, "已建议连接到 Wi-Fi", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "无法建议连接到 Wi-Fi", Toast.LENGTH_SHORT).show();
}
}
}

@ -0,0 +1,71 @@
package com.xypower.mpremote.adapter;
import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.xypower.mpremote.R;
import com.xypower.mpremote.interfaces.OnImageItemClickListener;
import com.xypower.mpremote.utils.ImageUtils;
import java.util.ArrayList;
import java.util.List;
public class ImageItemAdapter extends RecyclerView.Adapter<ImageItemAdapter.MyViewHolder> {
public int adapterType;
public OnImageItemClickListener listener;
private List<String> itemList = new ArrayList<>();
public class MyViewHolder extends RecyclerView.ViewHolder {
private ImageView imageView;
public MyViewHolder(View view, OnImageItemClickListener listener) {
super(view);
imageView = view.findViewById(R.id.id_imageview);
}
public ImageView getImage() {
return imageView;
}
}
public void setOnClickListener(OnImageItemClickListener listener) {
this.listener = listener;
}
public void setItemList(List<String> list) {
this.itemList = list;
notifyDataSetChanged();
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_img_item, parent, false);
return new MyViewHolder(view, listener);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, @SuppressLint("RecyclerView") int position) {
String s = itemList.get(position);
Glide.with(holder.itemView.getContext()).load(s).into(holder.getImage());
// holder.getImage().setImageDrawable(ImageUtils.loadDrawable(s));
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(v, position);
}
});
}
@Override
public int getItemCount() {
return itemList.size();
}
}

@ -0,0 +1,72 @@
package com.xypower.mpremote.adapter;
import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.xypower.mpremote.R;
import com.xypower.mpremote.bean.ChannelBean;
import com.xypower.mpremote.interfaces.OnItemClickListener;
import java.util.ArrayList;
import java.util.List;
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.MyViewHolder> {
public int adapterType;
public OnItemClickListener listener;
private List<ChannelBean> itemList = new ArrayList<>();
public class MyViewHolder extends RecyclerView.ViewHolder {
private TextView title;
public MyViewHolder(View view, OnItemClickListener listener) {
super(view);
title = view.findViewById(R.id.channel);
}
public TextView getTitle() {
return title;
}
}
public void setOnClickListener(OnItemClickListener listener) {
this.listener = listener;
}
public void setItemList(List<ChannelBean> list) {
this.itemList = list;
notifyDataSetChanged();
}
public void setAdapterType(int type) {
this.adapterType = type;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
return new MyViewHolder(view, listener);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, @SuppressLint("RecyclerView") int position) {
ChannelBean channelBean = itemList.get(position);
holder.getTitle().setText(channelBean.getChannelname());
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(v, channelBean,adapterType);
}
});
}
@Override
public int getItemCount() {
return itemList.size();
}
}

@ -0,0 +1,48 @@
package com.xypower.mpremote.bean;
import org.json.JSONObject;
public class ChannelBean {
public ChannelBean() {
}
String text;
int channel;
String channelname;
JSONObject jsonObject;
public JSONObject getJsonObject() {
return jsonObject;
}
public void setJsonObject(JSONObject jsonObject) {
this.jsonObject = jsonObject;
}
public String getChannelname() {
return channelname;
}
public void setChannelname(String channelname) {
this.channelname = channelname;
}
public int getChannel() {
return channel;
}
public void setChannel(int channel) {
this.channel = channel;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}

@ -0,0 +1,5 @@
package com.xypower.mpremote.interfaces;
public interface CompleteCallback {
void onResult();
}

@ -0,0 +1,10 @@
package com.xypower.mpremote.interfaces;
import android.view.View;
import com.xypower.mpremote.bean.ChannelBean;
public interface OnImageItemClickListener {
void onItemClick(View v, int position);
}

@ -0,0 +1,10 @@
package com.xypower.mpremote.interfaces;
import android.view.View;
import com.xypower.mpremote.bean.ChannelBean;
public interface OnItemClickListener {
void onItemClick(View v, ChannelBean position, int adapterType);
}

@ -0,0 +1,33 @@
package com.xypower.mpremote.utils;
import android.app.AlertDialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.xypower.mpremote.R;
public class AlertDialogUtils {
public static AlertDialog show(Context context,String hint) {
// 创建 AlertDialog
AlertDialog.Builder builder = new AlertDialog.Builder(context);
View dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_loading, null);
TextView viewById = dialogView.findViewById(R.id.tvLoading);
viewById.setText(hint);
builder.setView(dialogView);
builder.setCancelable(true); // 是否允许用户取消
AlertDialog loadingDialog = builder.create();
loadingDialog.show();
return loadingDialog;
}
public static void dismiss(AlertDialog loadingDialog) {
if (loadingDialog != null) {
loadingDialog.dismiss();
}
}
}

@ -0,0 +1,41 @@
package com.xypower.mpremote.utils;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import com.xypower.mpremote.R;
public class AppUtils {
/**
*
* @param context
* @return "1.0.0"
*/
public static String getVersionName(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return null;
}
}
/**
*
* @param context
* @return "我的App v1.0.0"
*/
public static String getAppNameWithVersion(Context context) {
try {
String appName = context.getString(R.string.app_name); // 从strings.xml获取
String versionName = getVersionName(context);
return String.format("%s v%s", appName, versionName);
} catch (Exception e) {
return context.getString(R.string.app_name);
}
}
}

@ -0,0 +1,93 @@
package com.xypower.mpremote.utils;
import android.app.AlertDialog;
import android.content.Context;
import android.os.CountDownTimer;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.xypower.mpremote.R;
public class CountdownAlertDialog {
private static AlertDialog dialog;
private static CountDownTimer countDownTimer;
private static OnCountdownFinishedListener listener;
// 显示对话框(静态方法)
public static void show(Context context,
int countdownSeconds, OnCountdownFinishedListener listener) {
// 先关闭已存在的对话框
dismiss();
// 创建 AlertDialog
AlertDialog.Builder builder = new AlertDialog.Builder(context);
View dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_loading, null);
TextView viewById = dialogView.findViewById(R.id.tvLoading);
viewById.setText("视频加载倒计时: " + countdownSeconds + " 秒");
builder.setView(dialogView);
builder.setCancelable(true); // 是否允许用户取消
dialog = builder.create();
dialog.show();
CountdownAlertDialog.listener = listener;
// 创建并显示对话框
if (countdownSeconds > 0) {
// 创建倒计时器
countDownTimer = new CountDownTimer(countdownSeconds * 1000, 1000) {
public void onTick(long millisUntilFinished) {
int secondsLeft = (int) (millisUntilFinished / 1000);
viewById.setText("视频加载倒计时: " + secondsLeft + " 秒");
}
public void onFinish() {
dismiss();
if (CountdownAlertDialog.listener != null) {
CountdownAlertDialog.listener.onCountdownFinished();
}
}
}.start();
}
// 设置对话框监听
dialog.setOnCancelListener(dialogInterface -> {
if (CountdownAlertDialog.listener != null) {
CountdownAlertDialog.listener.onCountdownCancelled();
}
dismiss();
});
dialog.setOnDismissListener(dialogInterface -> dismiss());
dialog.show();
}
// 关闭对话框(静态方法)
public static void dismiss() {
if (countDownTimer != null) {
countDownTimer.cancel();
countDownTimer = null;
}
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
dialog = null;
}
listener = null;
}
// 检查对话框是否显示中(静态方法)
public static boolean isShowing() {
return dialog != null && dialog.isShowing();
}
// 回调接口
public interface OnCountdownFinishedListener {
void onCountdownFinished();
default void onCountdownCancelled() {
}
}
}

@ -0,0 +1,284 @@
package com.xypower.mpremote.utils;
import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class FileUtils {
private static final String TAG = "FileUtils";
// /**
// * 拼接路径(使用 File 类)
// *
// * @param basePath 基础路径
// * @param subPath 子路径
// * @return 拼接后的路径
// */
// public static String join(String basePath, String subPath) {
// File file = new File(basePath, subPath);
// return file.getAbsolutePath();
// }
// public static String join(File basePath, String subPath) {
// File file = new File(basePath, subPath);
// return file.getAbsolutePath();
// }
/**
*
*
* @param dirPath /sdcard/Pictures
* @return List<File>
*/
public static List<String> getImageFiles(String dirPath) {
List<String> pathList = new ArrayList<>();
File dir = new File(dirPath);
// 检查文件夹是否存在
if (!dir.exists() || !dir.isDirectory()) {
Log.e("Error", "文件夹不存在或不是目录: " + dirPath);
return pathList;
}
// 遍历文件夹中的文件
File[] files = dir.listFiles();
if (files == null) return pathList;
List<File> imageFiles = Arrays.asList(files);
// 按最后修改时间降序排序(最新的在前)
Collections.sort(imageFiles, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return Long.compare(o2.lastModified(), o1.lastModified());
}
});
// 筛选图片文件(按扩展名)
for (File file : files) {
if (file.isFile() && isImageFile(file)) {
pathList.add(file.getAbsolutePath());
}
}
return pathList;
}
/**
*
*/
private static boolean isImageFile(File file) {
String name = file.getName().toLowerCase();
return name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".png") || name.endsWith(".webp") || name.endsWith(".gif") || name.endsWith(".bmp");
}
/**
*
*/
public static boolean isExternalStorageAvailable() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
/**
*
*/
public static File getCacheDir(Context context) {
return context.getCacheDir();
}
/**
*
*/
public static File getExternalFilesDir(Context context) {
return context.getExternalFilesDir(null);
}
/**
*
*
* @param dirPath
* @return
*/
public static boolean createDir(String dirPath) {
File dir = new File(dirPath);
if (!dir.exists()) {
return dir.mkdirs();
}
return true;
}
/**
*
*
* @param filePath
* @return
*/
public static boolean createFile(String filePath) {
File file = new File(filePath);
if (!file.exists()) {
try {
return file.createNewFile();
} catch (IOException e) {
Log.e(TAG, "Failed to create file: " + filePath, e);
return false;
}
}
return true;
}
/**
*
*
* @param filePath
* @return
*/
public static File getFile(String filePath) {
File file = new File(filePath);
File parentDir = file.getParentFile();
if (parentDir != null && !parentDir.exists()) {
boolean dirsCreated = parentDir.mkdirs(); // 递归创建所有父目录
if (!dirsCreated) {
Log.e("Error", "无法创建父目录: " + parentDir.getAbsolutePath());
return file;
}
}
if (!file.exists()) {
try {
boolean newFile = file.createNewFile();
if (newFile) {
return file;
}
} catch (IOException e) {
Log.e(TAG, "Failed to create file: " + filePath, e);
}
}
return file;
}
/**
*
*
* @param path
* @return
*/
public static boolean delete(String path) {
if (TextUtils.isEmpty(path)) {
return false;
}
File file = new File(path);
if (file.isDirectory()) {
return deleteDir(file);
} else {
return deleteFile(file);
}
}
/**
*
*/
private static boolean deleteDir(File dir) {
if (dir == null || !dir.exists() || !dir.isDirectory()) {
return false;
}
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteDir(file);
} else {
deleteFile(file);
}
}
}
return dir.delete();
}
/**
*
*/
private static boolean deleteFile(File file) {
return file != null && file.exists() && file.isFile() && file.delete();
}
/**
*
*
* @param srcPath
* @param destPath
* @return
*/
public static boolean copyFile(String srcPath, String destPath) {
File srcFile = new File(srcPath);
File destFile = new File(destPath);
if (!srcFile.exists() || !srcFile.isFile()) {
return false;
}
try (FileInputStream fis = new FileInputStream(srcFile); FileOutputStream fos = new FileOutputStream(destFile); FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
return true;
} catch (IOException e) {
Log.e(TAG, "Failed to copy file: " + srcPath, e);
return false;
}
}
/**
*
*
* @param oldPath
* @param newPath
* @return
*/
public static boolean rename(String oldPath, String newPath) {
File oldFile = new File(oldPath);
File newFile = new File(newPath);
return oldFile.exists() && oldFile.renameTo(newFile);
}
/**
*
*
* @param path
* @return
*/
public static long getFileSize(String path) {
File file = new File(path);
if (file.exists() && file.isFile()) {
return file.length();
}
return 0;
}
/**
*
*
* @param path
* @return
*/
public static long getDirSize(String path) {
File dir = new File(path);
if (dir.exists() && dir.isDirectory()) {
long size = 0;
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
size += getDirSize(file.getAbsolutePath());
} else {
size += file.length();
}
}
}
return size;
}
return 0;
}
}

@ -0,0 +1,26 @@
package com.xypower.mpremote.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
public class ImageUtils {
public static Drawable loadDrawable(String file) {
if (file == null || file.isEmpty()) {
return null;
}
Drawable drawable = null;
try {
Bitmap bitmap = BitmapFactory.decodeFile(file);
drawable = new BitmapDrawable(bitmap);
} catch (Exception e) {
e.printStackTrace();
}
return drawable;
}
}

@ -0,0 +1,4 @@
package com.xypower.mpremote.utils;
public class WifiUtils {
}

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- res/drawable/rounded_button.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 设置背景颜色 -->
<solid android:color="#C0C0C0" />
<!-- 设置圆角半径 -->
<corners android:radius="15dp" />
<!-- 可选:设置边框 -->
<stroke android:width="2dp" android:color="#fff" />
</shape>

@ -1,112 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView 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"
tools:context=".DeviceActivity">
<TextView
android:id="@+id/deviceInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:singleLine="false"
android:lineSpacingMultiplier="1.25"
android:textSize="16sp"
android:text=""
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/takePhoto1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
android:minWidth="72dp"
android:visibility="gone"
android:text="通道1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@+id/takeVideo1" />
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<Button
android:id="@+id/takePhoto2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:minWidth="72dp"
android:visibility="gone"
android:text="通道2"
app:layout_constraintStart_toEndOf="@+id/takePhoto1"
app:layout_constraintTop_toTopOf="@+id/takePhoto1" />
<TextView
android:id="@+id/deviceInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:lineSpacingMultiplier="1.25"
android:singleLine="false"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<Button
android:id="@+id/takePhoto3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:minWidth="72dp"
android:visibility="gone"
android:text="通道3"
app:layout_constraintStart_toEndOf="@+id/takePhoto2"
app:layout_constraintTop_toTopOf="@+id/takePhoto1" />
<Button
android:id="@+id/yt_control"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="云台控制"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/deviceInfo" />
<Button
android:id="@+id/takePhoto4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:minWidth="72dp"
android:visibility="gone"
android:text="通道4"
app:layout_constraintStart_toEndOf="@+id/takePhoto3"
app:layout_constraintTop_toTopOf="@+id/takePhoto1" />
<TextView
android:id="@+id/phototext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="拍照"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/deviceInfo" />
<Button
android:id="@+id/takeVideo1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="24dp"
android:minWidth="72dp"
android:visibility="gone"
android:text="视频1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewPhoto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:layout_marginRight="20dp"
app:layout_constraintTop_toBottomOf="@+id/phototext" />
<Button
android:id="@+id/takeVideo2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:minWidth="72dp"
android:visibility="gone"
android:text="视频2"
app:layout_constraintStart_toEndOf="@id/takeVideo1"
app:layout_constraintBottom_toBottomOf="@id/takeVideo1" />
<TextView
android:id="@+id/videotext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="视频预览"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/recyclerViewPhoto" />
<Button
android:id="@+id/takeVideo3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:minWidth="72dp"
android:visibility="gone"
android:text="视频3"
app:layout_constraintStart_toEndOf="@id/takeVideo2"
app:layout_constraintBottom_toBottomOf="@id/takeVideo1" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewVideo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:layout_marginRight="20dp"
app:layout_constraintTop_toBottomOf="@+id/videotext" />
<Button
android:id="@+id/takeVideo4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:minWidth="72dp"
android:visibility="gone"
android:text="视频4"
app:layout_constraintStart_toEndOf="@id/takeVideo3"
app:layout_constraintBottom_toBottomOf="@id/takeVideo1" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

@ -5,27 +5,28 @@
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ImageActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitStart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
<include
android:id="@+id/toolbar"
layout="@layout/toolbar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@android:drawable/screen_background_light_transparent" />
app:layout_constraintTop_toTopOf="parent" />
<!-- <ImageView-->
<!-- android:id="@+id/imageView"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:scaleType="fitStart"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:srcCompat="@android:drawable/screen_background_light_transparent" />-->
<ListView
android:id="@+id/imagesView"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
/>
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -4,40 +4,44 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<!-- 自定义 Toolbar -->
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
<Button
android:id="@+id/selectWifi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:textSize="20dp"
android:text="请先连接到设备的热点"
android:layout_marginLeft="15dp"
android:layout_marginTop="40dp"
android:layout_marginRight="15dp"
android:background="@drawable/rounded_button"
android:text="第一步连接WIFI"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
<Button
android:id="@+id/control"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="(绿色图标为当前已连接热点)"
android:layout_marginLeft="15dp"
android:layout_marginTop="40dp"
android:layout_marginRight="15dp"
android:background="@drawable/rounded_button"
android:text="第二步:进入控制页面"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
/>
app:layout_constraintTop_toBottomOf="@+id/selectWifi" />
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2"
app:layout_constraintBottom_toBottomOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,18 +1,110 @@
<?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">
<androidx.media3.ui.PlayerView
android:id="@+id/playerView"
<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" />-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:show_buffering="always"
app:show_timeout="5000"
app:use_controller="false"
app:resize_mode="fit"/>
android:layout_height="0dp"
android:layout_weight="1">
<TextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
</FrameLayout>
<!-- <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" />-->
<!-- <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"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:background="@mipmap/refresh"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintLeft_toLeftOf="parent"-->
<!-- app:layout_constraintRight_toRightOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:layout_constraintVertical_bias="0.9" />-->
<!-- <ImageButton-->
<!-- android:layout_marginBottom="10dp"-->
<!-- android:id="@+id/top"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:background="@mipmap/up"-->
<!-- app:layout_constraintBottom_toTopOf="@+id/center"-->
<!-- app:layout_constraintLeft_toLeftOf="@+id/center" />-->
<!-- <ImageButton-->
<!-- android:layout_marginRight="10dp"-->
<!-- android:id="@+id/left"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:background="@mipmap/left"-->
<!-- app:layout_constraintRight_toLeftOf="@+id/center"-->
<!-- app:layout_constraintTop_toTopOf="@+id/center" />-->
<!-- <ImageButton-->
<!-- android:layout_marginLeft="10dp"-->
<!-- android:id="@+id/right"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:background="@mipmap/right"-->
<!-- app:layout_constraintLeft_toRightOf="@+id/center"-->
<!-- app:layout_constraintTop_toTopOf="@+id/center" />-->
<!-- <ImageView-->
<!-- android:layout_marginTop="10dp"-->
<!-- android:id="@+id/bottom"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:background="@mipmap/down"-->
<!-- app:layout_constraintLeft_toLeftOf="@+id/center"-->
<!-- 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>

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Loading..."
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressbar" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:orientation="horizontal" >
<TextView
android:id="@+id/id_channel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="2dp"
android:text="" />
<TextView
android:id="@+id/id_datetime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="2dp"
android:text="" />
</LinearLayout>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="8dp"
android:paddingTop="20dp"
android:paddingRight="8dp"
android:paddingBottom="20dp">
<ImageView
android:id="@+id/id_imageview"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="2dp" />
</LinearLayout>

@ -1,25 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout 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:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:orientation="horizontal" >
android:layout_height="50dp">
<ImageView
android:id="@+id/id_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_wifi" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="5dp"
app:cardCornerRadius="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/id_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="2dp"
android:text="" />
<TextView
android:id="@+id/channel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="dfdsasdafdsd"
android:textSize="15sp"
tools:ignore="MissingConstraints" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,48 @@
<!-- res/layout/custom_title_bar.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="56dip"
android:background="@color/orange_dark"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/back"
android:layout_width="60dp"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@mipmap/back" />
</LinearLayout>
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:text="欣影遥控"
android:textColor="@color/white"
android:textSize="20sp" />
<LinearLayout
android:id="@+id/refresh"
android:layout_width="60dp"
android:layout_height="match_parent"
android:visibility="invisible">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="刷新"
android:textColor="@color/white"
android:textSize="25sp" />
</LinearLayout>
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -7,4 +7,11 @@
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="orange_standard">#FFA500</color>
<color name="orange_dark">#FF8C00</color>
<color name="orange_light">#FFD700</color>
<color name="orange_material_500">#FF9800</color>
<color name="orange_material_700">#F57C00</color>
<color name="orange_material_300">#FFB74D</color>
</resources>

@ -1,8 +1,8 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.MpRemote" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="Theme.MpRemote" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimary">@color/purple_700</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
@ -10,7 +10,14 @@
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
<!-- 自定义 Toolbar 样式 -->
<style name="MyToolbarStyle" parent="Widget.AppCompat.Toolbar">
<item name="titleTextColor">@color/white</item>
<item name="android:textSize">18sp</item>
<item name="android:background">@color/orange_light</item>
</style>
</resources>

@ -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