From e052d21b1a3d7a9c0c9bb62b5c4fd65ee2ba906c Mon Sep 17 00:00:00 2001 From: gaozl Date: Wed, 25 Mar 2026 14:50:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=97=A0SDK=E7=9A=84m?= =?UTF-8?q?ac=E8=8E=B7=E5=8F=96=E5=92=8CAPP=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 3 +- .../activity/program/ScreenSaverActivity.java | 1 - .../activity/program/ViewScreenSaver.java | 5 +- .../java/qianmu/container/app/Constant.java | 3 +- .../qianmu/container/mqtt/MQTTService.java | 60 ++-- .../qianmu/container/socket/SocketClient.java | 2 +- .../java/qianmu/container/util/AppUtil.java | 32 ++- .../qianmu/container/util/BitmapUtil.java | 64 +++++ .../qianmu/container/util/DeviceUtil.java | 144 ++-------- .../qianmu/container/util/SignWayUtil.java | 25 +- .../container/view/CustomerVideoView.java | 265 +++++------------- app/src/main/res/xml/file_paths.xml | 14 +- 12 files changed, 251 insertions(+), 367 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 91c4f24..9f10b45 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,7 +12,7 @@ android { minSdkVersion 24 targetSdkVersion 30 versionCode 6 - versionName "V2.0.8.19" + versionName "V2.0.8.20" // 2.0.8.2 修改网络连接证书设置、定时开关机设置 // 2.0.8.3 获取mac修改 // V2.0.8.5 同屏优化 @@ -20,6 +20,7 @@ android { // 2.0.8.12 固件更新 //V2.0.8.14 和义大道开机5分钟后重启应用 //V2.0.8.15 增加数字人视频模型 + //V2.0.8.20 修改无SDK的mac获取和APP更新 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' diff --git a/app/src/main/java/qianmu/container/activity/program/ScreenSaverActivity.java b/app/src/main/java/qianmu/container/activity/program/ScreenSaverActivity.java index 80b3391..5d1bc40 100644 --- a/app/src/main/java/qianmu/container/activity/program/ScreenSaverActivity.java +++ b/app/src/main/java/qianmu/container/activity/program/ScreenSaverActivity.java @@ -545,7 +545,6 @@ public class ScreenSaverActivity extends BaseActivity { } public void Screenshot(String time) { - String data3 = "{\"sn\":\"" + sn + "\",\"width\":1920,\"height\":1080,\"downLoadDirectoryPath\":\"/storage/emulated/0/\",\"pictureName\":\"screenShot.png\",\"pictureType\":1}"; instance.nvDownLoadScreenshotAsync(data3, new ViplexCore.CallBack() { diff --git a/app/src/main/java/qianmu/container/activity/program/ViewScreenSaver.java b/app/src/main/java/qianmu/container/activity/program/ViewScreenSaver.java index c452b53..afc988d 100644 --- a/app/src/main/java/qianmu/container/activity/program/ViewScreenSaver.java +++ b/app/src/main/java/qianmu/container/activity/program/ViewScreenSaver.java @@ -635,7 +635,7 @@ public class ViewScreenSaver extends ViewBase { return; } if (Constant.isMain) { - Log.e("TAG","主设备向其他设备发送图片下标"+imagePlayCount0); + LoggerUtil.e("TAG","主设备向其他设备发送图片下标"+imagePlayCount0); SocketServerManager.sendMessageToClient(LocSocCliManager.PROGRAM_IMG_INDEX, String.valueOf(imagePlayCount0)); } @@ -2707,8 +2707,9 @@ public class ViewScreenSaver extends ViewBase { binding.videoView0.setOnCompletionListener(new CustomerVideoView.OnCompletionListener() { @Override public void onCompletion() { + //判断如果是从设备且已连接,不切换 if(!LocSocCliManager.getLocalSocketState()){ - // 视频播放完成时的操作 + // 视频播放完成 handler.sendEmptyMessage(TYPE_UPDATE_VIDEO0); } } diff --git a/app/src/main/java/qianmu/container/app/Constant.java b/app/src/main/java/qianmu/container/app/Constant.java index 709729e..8336581 100644 --- a/app/src/main/java/qianmu/container/app/Constant.java +++ b/app/src/main/java/qianmu/container/app/Constant.java @@ -22,7 +22,7 @@ public class Constant { public static String screenType = "HDMI"; // 欣威视通3399设备为假关机 HDMI连接:可以用来判断是否为关机状态 LVDS连接:只能用定时关机时间来判断 public static String mqttState = ""; // 屏幕连接方式 public static String TTSHome="sbc"; // sbc-思必驰 kdxf-科大讯飞 (有语音的项目需要配置) - //public static String androidBoardType = ""; //设备板子型号 无固定版 + // public static String androidBoardType = ""; //设备板子型号 无固定版 public static String androidBoardType = "ys"; // 设备板子型号 ys(亿晟) 北京颐堤港定制touch // public static String androidBoardType = "xwst"; //设备板子型号 xwst(欣威视通3399) // public static String androidBoardType = "xwst2"; //设备板子型号 xwst2(欣威视通3588、T982、3576) @@ -91,7 +91,6 @@ public class Constant { public static final String NOVA_SCREEN_SHOT = "nova_screen_shot";// 诺瓦设备截屏 public static final String NOVA_SCREEN_POWER_ON = "nova_screen_power_on";// 诺瓦设备截屏 public static final String NOVA_SCREEN_POWER_CLOSE = "nova_screen_power_close";// 诺瓦设备截屏 - public static final String APP_SEND_PACKAGE_NAME = "action_send_packageName"; /** diff --git a/app/src/main/java/qianmu/container/mqtt/MQTTService.java b/app/src/main/java/qianmu/container/mqtt/MQTTService.java index 85dd863..a62215b 100644 --- a/app/src/main/java/qianmu/container/mqtt/MQTTService.java +++ b/app/src/main/java/qianmu/container/mqtt/MQTTService.java @@ -9,13 +9,19 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.Rect; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Binder; +import android.os.Build; import android.os.Environment; import android.os.IBinder; +import android.util.DisplayMetrics; import android.util.Log; +import android.view.Display; +import android.view.Surface; import android.view.View; import androidx.core.app.NotificationCompat; @@ -51,6 +57,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; +import java.lang.reflect.Method; import java.security.KeyManagementException; import java.security.KeyPair; import java.security.KeyStore; @@ -674,52 +681,41 @@ public class MQTTService extends Service { screenshotFuture = singleThreadExecutor.submit(() -> SignWayUtil.ysTakeScreenshot(path) ); - }else if("huidu".equals(Constant.androidBoardType)){ + }else if(!"".equals(Constant.androidBoardType)){ screenshotFuture = singleThreadExecutor.submit(() -> { - HuiduTech helper = new HuiduTech(MyApplication.getInstance()); - helper.screenCapture(path); - return true; - }); - }else if("bv".equals(Constant.androidBoardType)){ - screenshotFuture = singleThreadExecutor.submit(() ->{ - try{ - Bitmap screenBitmap = MyApplication.getInstance().getLztek().screenCapture(); - if(screenBitmap != null){ - try (FileOutputStream fos = new FileOutputStream(file)) { - screenBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); - fos.flush(); - } catch (IOException e) { - e.printStackTrace(); - } - if(new File(path).exists()){ - LoggerUtil.e("screenShot","截屏完成"); - return true; - } - } - }catch(Exception exp){ - LoggerUtil.e("screenShot",exp.getMessage()); + SignWayUtil.takeScreenshot(path); + if(new File(path).exists()){ + LoggerUtil.e("screenShot","截屏完成"); + return true; } return false; }); }else{ screenshotFuture = singleThreadExecutor.submit(() -> { + Bitmap screenBitmap = null; View decorView = MyApplication.getInstance().getCurrentActivity().getWindow().getDecorView(); if(decorView != null){ - decorView.setDrawingCacheEnabled(true); - decorView.buildDrawingCache(); - Bitmap bmp = decorView.getDrawingCache(); - Rect frame = new Rect(); - decorView.getWindowVisibleDisplayFrame(frame); - int statusBarHeight = frame.top; - Bitmap screenBitmap = Bitmap.createBitmap(bmp, 0, statusBarHeight, decorView.getWidth(), decorView.getHeight() - statusBarHeight); - decorView.setDrawingCacheEnabled(false); - //保存 + //调用截屏 + try { + decorView.setDrawingCacheEnabled(true); + decorView.buildDrawingCache(); + screenBitmap = Bitmap.createBitmap(decorView.getWidth(), decorView.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(screenBitmap); + decorView.draw(canvas); + decorView.setDrawingCacheEnabled(false); + } catch (Exception e) { + e.printStackTrace(); + } + } + //保存 + if(screenBitmap != null){ try (FileOutputStream fos = new FileOutputStream(file)) { screenBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.flush(); } catch (IOException e) { e.printStackTrace(); } + screenBitmap.recycle(); if(new File(path).exists()){ LoggerUtil.e("screenShot","截屏完成"); return true; diff --git a/app/src/main/java/qianmu/container/socket/SocketClient.java b/app/src/main/java/qianmu/container/socket/SocketClient.java index e0f43c6..17a0041 100644 --- a/app/src/main/java/qianmu/container/socket/SocketClient.java +++ b/app/src/main/java/qianmu/container/socket/SocketClient.java @@ -43,6 +43,7 @@ public class SocketClient extends WebSocketClient { @Override public void onClose(int code, String reason, boolean remote) { if (closeListener != null) closeListener.onClose(code, reason, remote); + LoggerUtil.e("OnClose:", "socket断开"); } @Override @@ -62,7 +63,6 @@ public class SocketClient extends WebSocketClient { public void setOnCloseListener(OnCloseListener closeListener) { this.closeListener = closeListener; - } public void setOnErrorListener(OnErrorListener errorListener) { diff --git a/app/src/main/java/qianmu/container/util/AppUtil.java b/app/src/main/java/qianmu/container/util/AppUtil.java index d13c7ea..a53267b 100644 --- a/app/src/main/java/qianmu/container/util/AppUtil.java +++ b/app/src/main/java/qianmu/container/util/AppUtil.java @@ -8,6 +8,7 @@ import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; +import android.provider.Settings; import android.util.Log; import androidx.core.content.FileProvider; @@ -132,12 +133,37 @@ public class AppUtil { * @param filePath */ public static void installApk(String filePath) { - //安装apk File apkFile = new File(filePath); + if (!apkFile.exists()) { + // 文件不存在 + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (!MyApplication.getInstance().getPackageManager().canRequestPackageInstalls()) { + LoggerUtil.e("installApp: ", "安装未知来源APP1"); + // 请求安装未知来源应用的权限 + Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); + intent.setData(Uri.parse("package:" + MyApplication.getInstance().getPackageName())); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MyApplication.getInstance().startActivity(intent); + return; + } + } else { + // 如果低于 Android 8.0,不处理 + return; + } + LoggerUtil.e("installApp: ", "安装未知来源APP2"); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); - MyApplication.getInstance().startActivity(intent); + // 使用 FileProvider 获取 content URI + String authority = MyApplication.getInstance().getPackageName() + ".fileprovider"; + Uri apkUri = FileProvider.getUriForFile(MyApplication.getInstance(), authority, apkFile); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); + // 检查是否有应用可以处理该Intent + if (intent.resolveActivity(MyApplication.getInstance().getPackageManager()) != null) { + MyApplication.getInstance().startActivity(intent); + } } } diff --git a/app/src/main/java/qianmu/container/util/BitmapUtil.java b/app/src/main/java/qianmu/container/util/BitmapUtil.java index 554ab92..772730a 100644 --- a/app/src/main/java/qianmu/container/util/BitmapUtil.java +++ b/app/src/main/java/qianmu/container/util/BitmapUtil.java @@ -31,6 +31,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.List; import jp.wasabeef.glide.transformations.RoundedCornersTransformation; import qianmu.container.R; @@ -329,4 +330,67 @@ public class BitmapUtil { e.printStackTrace(); } } + /** 横向拼接图片 + * + * @param bitmaps + * @return + */ + public static Bitmap mergeBitmaps(List bitmaps) { + if (bitmaps == null || bitmaps.isEmpty()) return null; + Bitmap bmp = bitmaps.get(0); + int width = bmp.getWidth(); + int height = bmp.getHeight(); + if(width > height){ + return mergeBitmapsVertical(bitmaps); + }else{ + return mergeBitmapsHorizontal(bitmaps); + } + } + + /** 横向拼接图片 + * + * @param bitmaps + * @return + */ + private static Bitmap mergeBitmapsHorizontal(List bitmaps) { + if (bitmaps == null || bitmaps.isEmpty()) return null; + int totalWidth = 0; + int maxHeight = 0; + for (Bitmap bmp : bitmaps) { + totalWidth += bmp.getWidth(); + maxHeight = Math.max(maxHeight, bmp.getHeight()); + } + Bitmap result = Bitmap.createBitmap(totalWidth, maxHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(result); + int currentX = 0; + for (Bitmap bmp : bitmaps) { + canvas.drawBitmap(bmp, currentX, 0, null); + currentX += bmp.getWidth(); + } + return result; + } + + /** 纵向拼接图片 + * + * @param bitmaps + * @return + */ + private static Bitmap mergeBitmapsVertical(List bitmaps) { + if (bitmaps == null || bitmaps.isEmpty()) return null; + int totalHeight = 0; + int maxWidth = 0; + for (Bitmap bmp : bitmaps) { + totalHeight += bmp.getHeight(); + maxWidth = Math.max(maxWidth, bmp.getWidth()); + } + Bitmap result = Bitmap.createBitmap(totalHeight, maxWidth, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(result); + int currentY = 0; + for (Bitmap bmp : bitmaps) { + canvas.drawBitmap(bmp, 0, currentY, null); + currentY += bmp.getHeight(); + } + return result; + } + } diff --git a/app/src/main/java/qianmu/container/util/DeviceUtil.java b/app/src/main/java/qianmu/container/util/DeviceUtil.java index f5e063a..501edf3 100644 --- a/app/src/main/java/qianmu/container/util/DeviceUtil.java +++ b/app/src/main/java/qianmu/container/util/DeviceUtil.java @@ -30,7 +30,7 @@ import java.net.SocketException; import java.util.Collections; import java.util.Enumeration; import java.util.List; - +import java.util.UUID; import qianmu.container.app.Constant; import qianmu.container.app.MyApplication; import qianmu.container.data.DeviceData; @@ -169,62 +169,34 @@ public class DeviceUtil { * 获取mac * */ public static String getMacFromHardware() { - String mac= ""; try { - String Localmac = getLocalMacAddress(); - if(Localmac!= null && !Localmac.equals("")){ - mac = Localmac.toLowerCase(); - }else{ - mac = getWifiMacAddress(); - } - LoggerUtil.e("mac获取","mac:"+mac); + String androidId = Settings.Secure.getString( + MyApplication.getInstance().getContentResolver(), + Settings.Secure.ANDROID_ID + ); + String uniqueId = getCustomUniqueId(androidId); + LoggerUtil.e("mac获取", "Android唯一设备ID:" + uniqueId); + return uniqueId; } catch (Exception e) { - mac=""; - LoggerUtil.e("mac获取","mac获取失败"+e.getMessage()); + LoggerUtil.e("mac获取", "获取唯一ID失败:" + e.getMessage()); } - return mac; + return null; } /** - * 根据IP地址获取MAC地址 - * @return + * 把 Android_ID 转成 类似 MAC 地址的格式(xx:xx:xx:xx:xx:xx) */ - public static String getLocalMacAddress() { - String macAddress=""; - macAddress = getMacFromNetworkInterface("wlan0"); - if (!StringUtil.isEmpty(macAddress)) { - return macAddress; + private static String getCustomUniqueId(String androidId) { + if (androidId == null || androidId.length() < 12) { + return UUID.randomUUID().toString().replace("-", ":").substring(0, 13); } - macAddress = getMacFromNetworkInterface("eth0"); - if (!StringUtil.isEmpty(macAddress)) { - return macAddress; - }else{ - macAddress = getLocalMacAddressFromIp(); - } - return macAddress; - } - - public static String getMacFromNetworkInterface(String interfaceName) { - try { - for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) { - if (interfaceName.equalsIgnoreCase(networkInterface.getName())) { - byte[] macBytes = networkInterface.getHardwareAddress(); - if (macBytes != null) { - StringBuilder sb = new StringBuilder(); - for (byte b : macBytes) { - sb.append(String.format("%02X:", b)); - } - if (sb.length() > 0) { - sb.deleteCharAt(sb.length() - 1); - } - return sb.toString(); - } - } - } - } catch (Exception e) { - e.printStackTrace(); + String raw = androidId.replaceAll("[^0-9A-Fa-f]", "").substring(0, 12); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < raw.length(); i += 2) { + sb.append(raw.substring(i, i + 2)).append(":"); } - return ""; + if (sb.length() > 0) sb.deleteCharAt(sb.length() - 1); + return sb.toString().toUpperCase(); } /** @@ -240,82 +212,6 @@ public class DeviceUtil { } } - /** - * 验证MAC地址格式 - */ - private static boolean isValidMacAddress(String mac) { - if (StringUtil.isEmpty(mac)) { - return false; - } - - // MAC地址正则表达式 - String macPattern = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"; - return mac.matches(macPattern); - } - - /** - * 格式化MAC地址 - */ - private static String formatMacAddress(String mac) { - if (StringUtil.isEmpty(mac)) { - return ""; - } - - // 移除所有分隔符 - String cleanMac = mac.replace(":", "").replace("-", "").replace(" ", ""); - - // 重新格式化为冒号分隔 - if (cleanMac.length() == 12) { - StringBuilder formatted = new StringBuilder(); - for (int i = 0; i < 12; i += 2) { - formatted.append(cleanMac.substring(i, i + 2)); - if (i < 10) { - formatted.append(":"); - } - } - return formatted.toString().toUpperCase(); - } - - return mac.toUpperCase(); - } - /** - * 根据IP地址获取MAC地址 - * @return - */ - public static String getLocalMacAddressFromIp() { - String strMacAddr = null; - try { - //获得IpD地址 - InetAddress ip = getLocalInetAddress(); - LoggerUtil.e("IP: ", ip.getHostAddress()); - byte[] b = NetworkInterface.getByInetAddress(ip).getHardwareAddress(); - StringBuffer buffer = new StringBuffer(); - for (int i = 0; i < b.length; i++) { - if (i != 0) { - buffer.append(':'); - } - String str = Integer.toHexString(b[i] & 0xFF); - buffer.append(str.length() == 1 ? 0 + str : str); - } - strMacAddr = buffer.toString().toUpperCase(); - } catch (Exception e) { - } - return strMacAddr; - } - - @SuppressLint("HardwareIds") - public static String getWifiMacAddress() { - try { - WifiManager wifiManager = (WifiManager) MyApplication.getInstance().getApplicationContext().getSystemService(Context.WIFI_SERVICE); - if (wifiManager != null) { - WifiInfo wifiInfo = wifiManager.getConnectionInfo(); - return wifiInfo.getMacAddress(); - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } /** * 获取移动设备本地IP * @return diff --git a/app/src/main/java/qianmu/container/util/SignWayUtil.java b/app/src/main/java/qianmu/container/util/SignWayUtil.java index 2c458ed..88e0702 100644 --- a/app/src/main/java/qianmu/container/util/SignWayUtil.java +++ b/app/src/main/java/qianmu/container/util/SignWayUtil.java @@ -2,6 +2,7 @@ package qianmu.container.util; import android.app.smdt.SmdtManagerNew; import android.content.Intent; +import android.graphics.Bitmap; import android.os.Build; import android.os.RemoteException; import android.os.signwaymanager.SignwayManager; @@ -17,6 +18,7 @@ import org.greenrobot.eventbus.EventBus; import java.io.BufferedReader; import java.io.File; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.text.SimpleDateFormat; @@ -282,12 +284,28 @@ public class SignWayUtil { SignwayManager signwayManager = SignwayManager.getInstance(MyApplication.getInstance()); signwayManager.takeScreenshot(path, 0); }else if(Constant.androidBoardType.equals("ys")){ - Log.e("TAG","开始截屏"); MyManager manager = MyManager.getInstance(MyApplication.getInstance()); boolean b = manager.takeScreenshot(path); - Log.e("TAG","截屏结果"+b); + }else if("huidu".equals(Constant.androidBoardType)){ + HuiduTech helper = new HuiduTech(MyApplication.getInstance()); + helper.screenCapture(path); + }else if(Constant.androidBoardType.equals("smt")){ + MyApplication.getInstance().getSmdt().disp_getScreenShot(path); + }else if("bv".equals(Constant.androidBoardType)){ + try{ + Bitmap screenBitmap = MyApplication.getInstance().getLztek().screenCapture(); + if(screenBitmap != null){ + try (FileOutputStream fos = new FileOutputStream(file)) { + screenBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }catch(Exception exp){ + LoggerUtil.e("screenShot",exp.getMessage()); + } } - } catch (Throwable t) { LoggerUtil.e("takeScreenshot", StringUtil.getThrowableStr(t)); } @@ -391,7 +409,6 @@ public class SignWayUtil { zcApi.getContext(MyApplication.getInstance()); return zcApi.getEthMacAddress("eth0"); }else if(Constant.androidBoardType.equals("huidu")){ - HuiduTech helper = new HuiduTech(MyApplication.getInstance()); if(!helper.getEthMac().isEmpty()){ String ethMac = helper.getEthMac(); diff --git a/app/src/main/java/qianmu/container/view/CustomerVideoView.java b/app/src/main/java/qianmu/container/view/CustomerVideoView.java index ad2ee99..a668b14 100644 --- a/app/src/main/java/qianmu/container/view/CustomerVideoView.java +++ b/app/src/main/java/qianmu/container/view/CustomerVideoView.java @@ -1,8 +1,8 @@ package qianmu.container.view; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Color; -import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; @@ -11,7 +11,6 @@ import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -import android.widget.VideoView; import androidx.annotation.NonNull; @@ -22,42 +21,24 @@ import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.StyledPlayerView; -/** - * Created by Android Studio. - * User: linzhibin - * Date: 2021/5/19 - * Time: 13:59 - */ public class CustomerVideoView extends FrameLayout { private StyledPlayerView playerView; private ExoPlayer player; + private TextureView mTextureView; // 我们自己管理 TextureView private OnCompletionListener onCompletionListener; private OnErrorListener onErrorListener; private OnPreparedListener onPreparedListener; private OnInfoListener onInfoListener; private Boolean isFirstFame = false; - // 监听器接口,保持与 VideoView 兼容 - public interface OnCompletionListener { - void onCompletion(); - } - - public interface OnErrorListener { - boolean onError(); - } - - public interface OnPreparedListener { - void onPrepared(); - } - - public interface OnInfoListener { - void onInfo(); - } - + // 接口保持不变 + public interface OnCompletionListener { void onCompletion(); } + public interface OnErrorListener { boolean onError(); } + public interface OnPreparedListener { void onPrepared(); } + public interface OnInfoListener { void onInfo(); } public CustomerVideoView(Context context) { super(context); @@ -75,121 +56,103 @@ public class CustomerVideoView extends FrameLayout { } private void init(Context context) { - // 创建 StyledPlayerView + // 1. 创建 StyledPlayerView playerView = new StyledPlayerView(context); addView(playerView); - // 初始化 ExoPlayer + // 2. 初始化播放器 initializePlayer(); - // 设置背景透明 + + // 透明设置 playerView.setBackgroundColor(Color.TRANSPARENT); playerView.setShutterBackgroundColor(Color.TRANSPARENT); - // 3. 设置使用透明背景 - playerView.setUseArtwork(false); // 如果不需要 artwork - playerView.setDefaultArtwork(null); // 清除默认 artwork - // 4. 设置 SurfaceView 透明 - View videoSurfaceView = playerView.getVideoSurfaceView(); - if (videoSurfaceView != null) { - videoSurfaceView.setBackgroundColor(Color.TRANSPARENT); - // 如果使用 SurfaceView,设置格式支持透明 - if (videoSurfaceView instanceof SurfaceView) { - Log.e("View: ","SurfaceView"); - SurfaceView surfaceView = (SurfaceView) videoSurfaceView; - replaceWithTextureView(playerView, (SurfaceView) surfaceView); - }else if (videoSurfaceView instanceof TextureView) { - // TextureView 默认支持透明 - videoSurfaceView.setBackgroundColor(Color.TRANSPARENT); - } - } + playerView.setUseArtwork(false); + playerView.setDefaultArtwork(null); + + replaceSurfaceWithTextureView(); } - private void replaceWithTextureView(StyledPlayerView playerView, SurfaceView oldSurface) { - ViewGroup parent = (ViewGroup) oldSurface.getParent(); - if (parent != null) { - // 创建新的 TextureView - TextureView textureView = new TextureView(getContext()); - textureView.setLayoutParams(oldSurface.getLayoutParams()); + /** + * 安全替换 SurfaceView 为 TextureView + */ + private void replaceSurfaceWithTextureView() { + if (playerView == null) return; + + // 延迟获取,确保 View 已创建 + playerView.post(() -> { + View videoView = playerView.getVideoSurfaceView(); + if (!(videoView instanceof SurfaceView)) return; + + SurfaceView oldSurface = (SurfaceView) videoView; + ViewGroup parent = (ViewGroup) oldSurface.getParent(); + if (parent == null) return; + + // 创建 TextureView + mTextureView = new TextureView(getContext()); + mTextureView.setLayoutParams(oldSurface.getLayoutParams()); // 替换 int index = parent.indexOfChild(oldSurface); parent.removeView(oldSurface); - parent.addView(textureView, index); + parent.addView(mTextureView, index); - // 设置到播放器 - if (playerView.getPlayer() != null) { - playerView.getPlayer().setVideoTextureView(textureView); + // 绑定到播放器 + if (player != null) { + player.setVideoTextureView(mTextureView); } - } + }); } private void initializePlayer() { - // 创建 ExoPlayer 实例 player = new SimpleExoPlayer.Builder(getContext()) .setTrackSelector(new DefaultTrackSelector(getContext())) .build(); player.setPlayWhenReady(false); - // 设置播放器到 PlayerView playerView.setPlayer(player); setupPlayerListeners(); - // 默认设置(可以根据需要调整) playerView.setUseController(false); - // 默认设置 Surface 在底部 - setZOrderOnTop(false); - setZOrderMediaOverlay(false); } private void setupPlayerListeners() { if (player == null) return; - // 播放完成监听 player.addListener(new Player.Listener() { @Override public void onPlaybackStateChanged(int playbackState) { if (playbackState == Player.STATE_ENDED) { - if (onCompletionListener != null) { + if (onCompletionListener != null) onCompletionListener.onCompletion(); - } isFirstFame = false; } - if(playbackState == Player.STATE_READY){ - Log.e("loadVideo: ","STATE_READY"); - // 视频准备就绪 - if (onPreparedListener != null) { + if (playbackState == Player.STATE_READY) { + if (onPreparedListener != null) onPreparedListener.onPrepared(); - } setVisibility(VISIBLE); - if(playerView != null && !isFirstFame){ + if (playerView != null && !isFirstFame) playerView.setAlpha(0f); - } } } @Override public void onPlayerError(@NonNull PlaybackException error) { - if (onErrorListener != null) { + if (onErrorListener != null) onErrorListener.onError(); - } } + @Override public void onRenderedFirstFrame() { - Log.e("loadVideo: ","FirstFrame"); - if (onInfoListener != null) { + if (onInfoListener != null) onInfoListener.onInfo(); - } isFirstFame = true; - if(playerView != null){ + if (playerView != null) playerView.setAlpha(1f); - } } }); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // 保持原有的测量逻辑 int width = getDefaultSize(0, widthMeasureSpec); int height = getDefaultSize(0, heightMeasureSpec); setMeasuredDimension(width, height); - - // 让 StyledPlayerView 填满整个父布局 if (playerView != null) { playerView.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), @@ -201,151 +164,71 @@ public class CustomerVideoView extends FrameLayout { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - // 布局 StyledPlayerView 使其填满整个控件 - if (playerView != null) { + if (playerView != null) playerView.layout(0, 0, getWidth(), getHeight()); - } } - // ==================== 实现 VideoView 的常用方法 ==================== - - // 1. setBackground - 设置背景 @Override public void setBackground(Drawable background) { super.setBackground(background); - // 如果需要设置 PlayerView 的背景 - if (playerView != null) { - playerView.setVisibility(GONE); - } + if (playerView != null) playerView.setVisibility(GONE); } - public void clearBackground(){ + public void clearBackground() { super.setBackground(null); } @Override public void setBackgroundColor(int color) { super.setBackgroundColor(color); - if (color != Color.TRANSPARENT && playerView != null) { + if (color != Color.TRANSPARENT && playerView != null) playerView.setVisibility(GONE); - } } @Override public void setBackgroundResource(int resid) { super.setBackgroundResource(resid); - if (playerView != null) { + if (playerView != null) playerView.setVisibility(INVISIBLE); - } } - // 2. setZOrderOnTop - 设置 Surface 是否在最顶层 - public void setZOrderOnTop(boolean onTop) { - if (playerView != null) { - // 获取 PlayerView 内部的 SurfaceView - View videoSurfaceView = playerView.getVideoSurfaceView(); - if (videoSurfaceView instanceof SurfaceView) { - SurfaceView surfaceView = (SurfaceView) videoSurfaceView; - surfaceView.setZOrderOnTop(onTop); - } - } - } + public void setZOrderOnTop(boolean onTop) {} + public void setZOrderMediaOverlay(boolean isMediaOverlay) {} - // 3. setZOrderMediaOverlay - 设置 Surface 作为媒体叠加层 - public void setZOrderMediaOverlay(boolean isMediaOverlay) { - if (playerView != null) { - View videoSurfaceView = playerView.getVideoSurfaceView(); - if (videoSurfaceView instanceof SurfaceView) { - SurfaceView surfaceView = (SurfaceView) videoSurfaceView; - surfaceView.setZOrderMediaOverlay(isMediaOverlay); - } - } - } - - // 4. setVisibility - 设置可见性 @Override public void setVisibility(int visibility) { super.setVisibility(visibility); - if (playerView != null) { + if (playerView != null) playerView.setVisibility(visibility); - } } - // 5. 视频控制相关方法 public void setVideoPath(String path) { if (player != null && path != null) { - try { - MediaItem mediaItem = MediaItem.fromUri(path); - player.setMediaItem(mediaItem); - player.prepare(); - player.setPlaybackParameters(new PlaybackParameters(1.0f)); - } catch (Exception e) {} - } - } - - - public void start() { - if (player != null) { - player.play(); - } - } - - public void pause() { - if (player != null) { - player.pause(); - } - } - - public void stopPlayback() { - if (player != null) { - player.stop(); - } - } - - public void seekTo(int position) { - if (player != null) { - player.seekTo(position); + MediaItem mediaItem = MediaItem.fromUri(path); + player.setMediaItem(mediaItem); + player.prepare(); + player.setPlaybackParameters(new PlaybackParameters(1.0f)); } } - public boolean isPlaying() { - return player != null && player.isPlaying(); - } - - // 6. 监听器设置 - public void setOnCompletionListener(OnCompletionListener listener) { - this.onCompletionListener = listener; - } - - public void setOnPreparedListener(OnPreparedListener listener) { - this.onPreparedListener = listener; - } + public void start() { if (player != null) player.play(); } + public void pause() { if (player != null) player.pause(); } + public void stopPlayback() { if (player != null) player.stop(); } + public void seekTo(int position) { if (player != null) player.seekTo(position); } + public boolean isPlaying() { return player != null && player.isPlaying(); } - public void setOnInfoListener(OnInfoListener listener) { - this.onInfoListener = listener; - } - - public void setOnErrorListener(OnErrorListener listener) { - this.onErrorListener = listener; - } - - // 可选:添加与 ExoPlayer 兼容的错误监听器 - public void setOnExoPlayerErrorListener(Player.Listener errorListener) { - if (player != null && errorListener != null) { - player.addListener(errorListener); - } - } + public void setOnCompletionListener(OnCompletionListener listener) { this.onCompletionListener = listener; } + public void setOnPreparedListener(OnPreparedListener listener) { this.onPreparedListener = listener; } + public void setOnInfoListener(OnInfoListener listener) { this.onInfoListener = listener; } + public void setOnErrorListener(OnErrorListener listener) { this.onErrorListener = listener; } public void release() { if (player != null) { + player.setVideoTextureView(null); player.release(); player = null; } - if (playerView != null) { - playerView.setPlayer(null); - } - onCompletionListener = null; - onErrorListener = null; + if (playerView != null) playerView.setPlayer(null); } @Override @@ -354,7 +237,6 @@ public class CustomerVideoView extends FrameLayout { release(); } - // 重置播放器 public void reset() { if (player != null) { player.stop(); @@ -362,12 +244,7 @@ public class CustomerVideoView extends FrameLayout { } } - // 设置音量 public void setVolume(float volume) { - if (player != null) { - player.setVolume(volume); - } + if (player != null) player.setVolume(volume); } - -} - +} \ No newline at end of file diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml index 42dc376..0644416 100644 --- a/app/src/main/res/xml/file_paths.xml +++ b/app/src/main/res/xml/file_paths.xml @@ -1,6 +1,14 @@ - - - + + + + + + + + + + + \ No newline at end of file