diff --git a/XixunPlayer/app/build.gradle b/XixunPlayer/app/build.gradle index 2951d8e..60361ae 100644 --- a/XixunPlayer/app/build.gradle +++ b/XixunPlayer/app/build.gradle @@ -11,7 +11,7 @@ android { minSdk 21 targetSdk 34 versionCode 1 - versionName "2.2.4-N" + versionName "2.2.16-N" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/XixunPlayer/app/src/main/AndroidManifest.xml b/XixunPlayer/app/src/main/AndroidManifest.xml index 47da6ae..349ed56 100644 --- a/XixunPlayer/app/src/main/AndroidManifest.xml +++ b/XixunPlayer/app/src/main/AndroidManifest.xml @@ -10,6 +10,11 @@ android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" /> + + + + + + + + + + + Android传参设置宽高 + + + + +
正在接收Android宽高...
+ +
+ +
+ + + +
+
+
+ +
+
+
设备ID
+
+ 3568S-2C5-00641 +
+
+
+ +
+
+ +
+
+
网络状态
+
+ 未知网络 +
+
+
+ + +
+
+ +
+
+
IP地址
+
+ 0.0.0.0 +
+
+
+
+
+
+
+ +
+ + + +
+
云平台发布
+
+
+
+ 📌 + 操作步骤 +
+
+
1.登录到云平台
+
2.创建并编辑节目
+
3.发送节目到设备
+
+
+
+
+ + + + + +
+
+
+
+ 📌 + 注意事项 +
+
+ 设备需切换至联网模式并关联云平台账号,云服务器地址:https://www.ledokcloud.com +
+
+
+
+
+ + +
+ + + +
+
手机APP发布
+
+
+
+ 📌 + 操作步骤 +
+
+
1.打开LEDOK Lite APP
+
2.创建并编辑节目
+
3.发送节目到设备
+
+
+
+
+ + + + + +
+
+
+
+
+ 📌 + 注意事项 +
+
+ 设备需切换至热点模式,手机需连接该设备热点 +
+
+
+
+ + +
+
+
+
+
+ + +
+ + + +
+
U盘发布
+
+
+
+ 📌 + 操作步骤 +
+
+
1.节目文件存入U盘
+
2.U盘接入设备
+
3.系统自动播放
+
+
+
+
+ + + + + +
+
+
+
+ 📌 + 注意事项 +
+
+ 无设备模式限制,U盘即插即播 +
+
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/AIDLService.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/AIDLService.java index 9994042..a2e3a89 100644 --- a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/AIDLService.java +++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/AIDLService.java @@ -13,9 +13,13 @@ import com.xixun.util.PlayerInfo; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; +import java.util.Date; import java.util.HashSet; import gnph.util.IOs; @@ -102,7 +106,7 @@ public class AIDLService extends Service { public void setUSBProgramPwd(String pwd) throws RemoteException { } - + SimpleDateFormat fmt = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); @SuppressLint("ResourceType") @Override public String executeJosnCommand(String jsonstr) throws RemoteException { @@ -186,17 +190,22 @@ public class AIDLService extends Service { if(t>0) { var len = new File(Util.programDir+"/"+src.filename).length(); conn.setHeader("Range", "bytes="+len+"-"); - Util.println(" Range "+len); + Util.println(" Range: bytes="+len+"-"); } var input = conn.in(); var code = conn.hconn().getResponseCode(); - var fout = new FileOutputStream(Util.programDir+"/"+src.filename, code==206); - IOs.writeCloseIn(fout, input); - fout.flush(); - fout.getFD().sync(); - fout.close(); - if(code==200 || code==206) break; - else Util.println(" error "+code+" "+conn.hconn().getResponseMessage()+" size: "+new File(src.filename).length()+" "+src.filename); + Util.println(" "+code+" - "+conn.hconn().getResponseMessage()); + if(code==200 || code==206) { + var fOut = new FileOutputStream(Util.programDir+"/"+src.filename, code==206); + IOs.writeCloseIn(fOut, input); + fOut.flush(); + fOut.getFD().sync(); + fOut.close(); + break; + } else { + Util.println(" body "+IOs.readStrClose(input)); + Util.println(" size: "+new File(Util.programDir+"/"+src.filename).length()+" "+src.filename); + } } catch (Throwable e) { Util.println(Util.toStr(e)); } @@ -247,6 +256,8 @@ public class AIDLService extends Service { try { var fOut = new FileOutputStream(Util.programDir + "/program"); fOut.write(jsonBytes); + var spaces = " ".getBytes(); + for(int i=0;i<1000; i++) fOut.write(spaces); fOut.flush(); fOut.getFD().sync(); fOut.close(); @@ -337,6 +348,51 @@ public class AIDLService extends Service { "cardId", Util.getCardId(), "commandId", commandId ).toString(); + } else if(_type.equalsIgnoreCase("GetLog")) { + return new JSMap( + "_type", "Success", + "cardId", Util.getCardId(), + "commandId", commandId, + "text", Util.buf.toString() + ).toString(); + } else if(_type.equalsIgnoreCase("ListProgFiles")) { + var files = new File(Util.programDir).listFiles(); + var writer = new StringBuilder(); + if(files!=null) { + Arrays.sort(files, (f1, f2) -> Long.signum(f2.lastModified() - f1.lastModified())); + for(var file : files) writer.append(fmt.format(new Date(file.lastModified()))).append(' ').append(file.getName()).append(' ').append(String.valueOf(file.length())).append('\n'); + } + return new JSMap( + "_type", "Success", + "cardId", Util.getCardId(), + "commandId", commandId, + "text", writer.toString() + ).toString(); + } else if("GetJson".equalsIgnoreCase(_type)) { + var inse = new File(Util.programDir+"/insert"); + var files = new File(Util.programDir).listFiles(); + var writer = new StringBuilder(); + if(inse.isFile()) { + var text = IOs.readStrClose(new FileInputStream(inse)).trim(); + if(files!=null) for(var file : files) if(! "program".equals(file.getName())) text = text.replace("\""+file.getName()+"\"", "\""+file.getName()+"\"/*"+file.length()+" "+fmt.format(new Date(file.lastModified()))+"*/"); + writer.append("insert:\n"); + writer.append(text); + if(! text.endsWith("\n")) writer.append("\n"); + } + var prog = new File(Util.programDir+"/program"); + if(! prog.isFile()) writer.append("'program' file not exist"); + else { + var text = IOs.readStrClose(new FileInputStream(prog)).trim(); + if(files!=null) for(var file : files) if(! "program".equals(file.getName())) text = text.replace("\""+file.getName()+"\"", "\""+file.getName()+"\"/*"+file.length()+" "+fmt.format(new Date(file.lastModified()))+"*/"); + writer.append("\nprogram:\n"); + writer.append(text); + } + return new JSMap( + "_type", "Success", + "cardId", Util.getCardId(), + "commandId", commandId, + "text", writer.toString() + ).toString(); } return new JSMap( "_type", "Error", @@ -391,7 +447,7 @@ public class AIDLService extends Service { @Override public String getProgramTask() throws RemoteException { try { - return IOs.readStrClose(new FileInputStream(Util.programDir+"/program")); + return IOs.readStrClose(new FileInputStream(Util.programDir+"/program")).trim(); } catch (Exception e) { Util.printStackTrace(e); return Util.toStr(e); diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainActivity.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainActivity.java index 9aefca3..c64fb45 100644 --- a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainActivity.java +++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainActivity.java @@ -11,6 +11,7 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.graphics.Color; import android.media.AudioFocusRequest; import android.media.AudioManager; import android.os.Build; @@ -19,6 +20,8 @@ import android.os.IBinder; import android.os.StrictMode; import android.view.Choreographer; import android.view.View; +import android.webkit.JavascriptInterface; +import android.webkit.WebView; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; @@ -40,6 +43,7 @@ import java.util.LinkedList; import gnph.util.Chsets; import gnph.util.JSList; import gnph.util.JSMap; +import gnph.util.O; public class MainActivity extends Activity implements Choreographer.FrameCallback { @@ -49,9 +53,9 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac public Intent environIntent = new Intent(); HashSet environs = new HashSet<>(); BackView backView; + WebView backViewL; Prog progView, insView; long launchMilli = System.currentTimeMillis(); - long syncMs; int state; @Override @@ -75,7 +79,8 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, android.Manifest.permission.RECEIVE_BOOT_COMPLETED, - android.Manifest.permission.INTERNET + android.Manifest.permission.INTERNET, + android.Manifest.permission.ACCESS_COARSE_LOCATION }, 999); } } @@ -136,6 +141,11 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac progView.release(); progView = null; } + if(backViewL!=null) { + backViewL.removeAllViews(); + backViewL.destroy(); + backViewL = null; + } ins = null; for(var rece : reces) unregisterReceiver(rece); for(var service : services) unbindService(service); @@ -158,23 +168,29 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac protected void onStop() { super.onStop(); Util.println(" ==<< MainActivity onStop << "+hashCode()); + state = 8; + stopProg(); } @Override protected void onRestart() { super.onRestart(); Util.println(" ==>> MainActivity onRestart >> "+hashCode()); + if(progView==null && insView==null) runOnUiThread(this::initProg); } @Override protected void onResume() { super.onResume(); Util.println(" ==>> MainActivity onResume >> "+hashCode()); + if(progView==null && insView==null) runOnUiThread(this::initProg); } @Override protected void onPause() { super.onPause(); Util.println(" ==<< MainActivity onPause << "+hashCode()); + state = 8; + stopProg(); } CardService serviCard; @@ -186,6 +202,86 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac Util.initDir(this); if(MainService.ins==null) startService(new Intent(this, MainService.class)); + var cardId = Util.getCardId(); + if(! cardId.isEmpty()) Util.cardType = cardId.toLowerCase().charAt(0); + if(Util.cardType=='l') { + backViewL = new WebView(this); + backViewL.setBackgroundColor(Color.TRANSPARENT); + backViewL.setVerticalScrollBarEnabled(false); + backViewL.setHorizontalScrollBarEnabled(false); + backViewL.setInitialScale(100); + backViewL.setPadding(0, 0, 0, 0); + backViewL.clearCache(true); + + var settings = backViewL.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setAllowFileAccess(true); + settings.setDomStorageEnabled(true); + settings.setBuiltInZoomControls(false); + settings.setDisplayZoomControls(false); + settings.setSupportZoom(false); + settings.setUseWideViewPort(true); + settings.setLoadsImagesAutomatically(true); + settings.setLoadWithOverviewMode(true); + // 添加JS交互接口(核心:传递宽高) + backViewL.addJavascriptInterface(new Object() { + @JavascriptInterface + public String getSizeFromAndroid() { + if(serviCard!=null && serviCard.asBinder().isBinderAlive()) { + try { + var www = serviCard.getScreenWidth(); + var hhh = serviCard.getScreenHeight(); + if(www!=Util.screenWidth || hhh!=Util.screenHeight) { + Util.screenWidth = www; + Util.screenHeight = hhh; + ins.backView.invalidate(); + } + } catch(Throwable e) { + Util.printStackTrace(e); + } + } else { + Util.println(" bindService"); + ins.bindService(ins.intenCard, ins.connCard, Context.BIND_AUTO_CREATE); + } + return "{\"width\":\"" + Util.screenWidth + "px\",\"height\":\"" + Util.screenHeight + "px\"}"; + } + @JavascriptInterface + public String getNetwork() { + // 获取当前网络类型 + NetworkTypeUtil.NetworkType networkType = NetworkTypeUtil.getCurrentNetworkType(MainActivity.this); + String value = ""; + switch (networkType) { + case WIFI: + value = NetworkTypeUtil.getWifiSsid(MainActivity.this); + // 处理未获取到SSID的情况 + if (O.isEmpty(value)) value = "未知WiFi名称"; + System.out.println("当前是 WiFi 网络:" + value); + break; + case ETHERNET: + System.out.println("当前是有线网络"); + break; + case CELLULAR: + // 细分蜂窝网络类型(2G/3G/4G) + String cellularSubType = NetworkTypeUtil.getCellularSubType(MainActivity.this); + value = cellularSubType; + System.out.println("当前是蜂窝网络:" + cellularSubType); + break; + case NONE: + System.out.println("无网络连接"); + break; + case UNKNOWN: + System.out.println("未知网络类型(如蓝牙、VPN)"); + break; + } + var IP = NetworkTypeUtil.getActiveNetworkIp(MainActivity.this); + return "{\"type\":\"" + networkType + "\",\"value\":\""+value+"\",\"ip\":\""+IP+"\",\"id\":\""+cardId+"\"}"; + } + @JavascriptInterface + public void onJsCallback(String message) { + } + }, "AndroidBridge"); + } + var cfg = new File(Util.programDir+"/cfg"); if(! cfg.isFile()) Util.cfg = new JSMap(); else { @@ -235,6 +331,10 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac backView.invalidate(); if(Util.isScreenOn) initProg(); else state = 8; + if(backView.isShown() && backViewL!=null) { + setContentView(backViewL); + backViewL.loadUrl("file:///android_asset/local_page.html"); + } } catch (Exception e) { Util.makeText(MainActivity.this, Util.toStr(e)).show(); Util.printStackTrace(e); @@ -285,17 +385,8 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac Util.println(" IsScreenOn: "+Util.isScreenOn); if(! Util.isScreenOn) { state = 8; - if(insView!=null) { - insView.release(); - insView = null; - } - if(progView!=null) { - progView.release(); - progView = null; - } - setContentView(backView); - } else if(progView==null && insView==null) MainActivity.ins.runOnUiThread(() -> initProg()); - System.gc(); + stopProg(); + } else if(progView==null && insView==null) runOnUiThread(() -> initProg()); } }, new IntentFilter("com.xixun.action.PAUSE_PLAYER")); reces.add(rece); @@ -363,11 +454,11 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac if(! avas.isEmpty()) { var page = avas.get(curAva); for(var layer : page.layers) for(var src : layer.srcs) if(src.isShow) { - showHides.add(new MainActivity.ShowHide(ms+1000, src, 'H')); + showHides.add(new MainActivity.ShowHide(ms+500, src, 'H')); src.isShow = false; } if(code > 0 && avas.get(curAva)==progView.pages.get(code-1)) { - showHides.add(new ShowHide(ms + 1000, ()->{ + showHides.add(new ShowHide(ms + 500, ()->{ for(var call : progView.calls) call.doFrame(ms); })); } @@ -390,6 +481,8 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac root.put("Demand", code); try (var fOut = new FileOutputStream(Util.programDir + "/program")) { root.write(fOut); + var spaces = " ".getBytes(); + for(int i=0;i<1000; i++) fOut.write(spaces); fOut.flush(); fOut.getFD().sync(); } @@ -478,6 +571,8 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac AudioFocusRequest audioFocusRequest; public void stopProg() { avas.clear(); + showHides.clear(); + showeds.clear(); curAva = 0; curTimes = 1; if(insView!=null) { @@ -488,7 +583,11 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac progView.release(); progView = null; } - setContentView(backView); + if(backViewL==null) setContentView(backView); + else { + setContentView(backViewL); + backViewL.loadUrl("file:///android_asset/local_page.html"); + } System.gc(); } public boolean delProgFile() { @@ -498,11 +597,13 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac if(files != null) for(var file : files) if(! file.delete()) ok = false; state = 4; try { - var out = new FileOutputStream(Util.programDir+"/program"); - out.write("{}".getBytes()); - out.flush(); - out.getFD().sync(); - out.close(); + var fOut = new FileOutputStream(Util.programDir+"/program"); + fOut.write("{}".getBytes()); + var spaces = " ".getBytes(); + for(int i=0;i<1000; i++) fOut.write(spaces); + fOut.flush(); + fOut.getFD().sync(); + fOut.close(); } catch (Throwable ignored) { } return ok; @@ -547,7 +648,7 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac state = 5; Util.println("Init Sync"); var ms = System.currentTimeMillis(); - if(demand==0 || progView==null) syncProg((ms+999)/1000*1000, 0); + if(demand==0 || progView==null) syncProg(ms, 0); else { avas.clear(); var page = progView.pages.get(demand-1); @@ -609,14 +710,14 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac } public void afterCheck(Prog view, byte[] json) { try { - var ms = (System.currentTimeMillis()+999)/1000*1000; + var ms = System.currentTimeMillis(); if(view.isInsert) { if(insView!=null) insView.release(); insView = view; try { var page = avas.get(curAva); for(var layer : page.layers) for(var src : layer.srcs) if(src.isShow) { - showHides.add(new ShowHide(ms+1000, src, 'H')); + showHides.add(new ShowHide(ms+500, src, 'H')); src.isShow = false; } } catch (Throwable ignored) {} @@ -628,6 +729,8 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac } var fOut = new FileOutputStream(Util.programDir + (view.isInsert?"/insert":"/program")); fOut.write(json); + var spaces = " ".getBytes(); + for(int i=0;i<1000; i++) fOut.write(spaces); fOut.flush(); fOut.getFD().sync(); fOut.close(); @@ -671,6 +774,8 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac } } public LinkedList showHides = new LinkedList<>(); + public HashSet showeds = new HashSet<>(); + boolean contains(Prog.Source src, char act) { for(var showHide : showHides) if(showHide.src==src && showHide.act==act) return true; return false; @@ -683,17 +788,28 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac while(iter.hasNext()) { var showHide = iter.next(); if(showHide.time > milli) { - if(showHide.time > milli+250) break; + if(showHide.time > milli+400) break; else continue; } if(showHide.act=='H') { showHide.src.hide(); + showeds.remove(showHide.src); if(! contains(showHide.src, 'S')) showHide.src.release(); iter.remove(); } else if(showHide.act=='S') { showHide.src.show(); + showeds.add(showHide.src); iter.remove(); - } else if(showHide.run!=null) showHide.run.run(); + } else if(showHide.run!=null) { + showHide.run.run(); + iter.remove(); + } + } + var iterator = showeds.iterator(); + while(iterator.hasNext()) { + var showed = iterator.next(); + if(showed.view==null || ! showed.view.isShown()) iterator.remove(); + else showed.doEff(); } boolean noProg = progView == null && insView==null; if(noProg && showHides.isEmpty()) { @@ -717,7 +833,7 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac if(milli < lastPage.endMilli) lastPage.showHideSrcs(milli, showHides); else { for(var layer : lastPage.layers) for(var src : layer.srcs) if(src.isShow) { - showHides.add(new ShowHide(lastPage.endMilli+1000, src, 'H')); + showHides.add(new ShowHide(lastPage.endMilli+500, src, 'H')); src.isShow = false; } if(isInsert) { @@ -765,7 +881,7 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac for(int i=0; i{ + showHides.add(new ShowHide(milli + 500, ()->{ iii.removeView(src.view); if(src.view instanceof Choreographer.FrameCallback) iii.calls.remove((Choreographer.FrameCallback) src.view); })); @@ -774,7 +890,7 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac } if(insView.pages.isEmpty()) { var iii = insView; - showHides.add(new ShowHide(milli + 1000, iii::release)); + showHides.add(new ShowHide(milli + 500, iii::release)); insView = null; try { var fOut = new FileOutputStream(Util.programDir + "/insert"); @@ -820,7 +936,6 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac start += avas.get(curAva++).tDur; } while(curAva < avas.size() && start<=milli); start -= avas.get(--curAva).tDur; - syncMs = milli; Util.println("Sync. dur: "+dur+" milli: "+milli+" start: "+start+" diff: "+(milli - start)); } avas.get(curAva).setMillis(start, milli, showHides); @@ -832,7 +947,11 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac } waitTo = milli + 1000; if(state!=2) { - setContentView(backView); + if(backViewL==null) setContentView(backView); + else { + setContentView(backViewL); + backViewL.loadUrl("file:///android_asset/local_page.html"); + } state = 2; } Util.println("No Avas, back"); diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainService.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainService.java index 3b97e8a..c19645a 100644 --- a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainService.java +++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainService.java @@ -87,6 +87,8 @@ public class MainService extends Service { else { var fOut = new FileOutputStream(Util.programDir + "/program"); fOut.write(json); + var spaces = " ".getBytes(); + for(int i=0;i<1000; i++) fOut.write(spaces); fOut.flush(); fOut.getFD().sync(); fOut.close(); diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/NetworkTypeUtil.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/NetworkTypeUtil.java new file mode 100644 index 0000000..d3a3556 --- /dev/null +++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/NetworkTypeUtil.java @@ -0,0 +1,283 @@ +package com.xixun.xixunplayer; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +/** + * 网络类型判断工具类(Java 版本,兼容低编译版本,无需 API 29) + */ +public class NetworkTypeUtil { + + /** + * 网络类型枚举 + */ + public enum NetworkType { + WIFI, // WiFi 网络 + ETHERNET, // 有线网络(网线、USB 共享等) + CELLULAR, // 蜂窝网络(2G/3G/4G) + NONE, // 无网络 + UNKNOWN // 未知网络(蓝牙、VPN 等) + } + + /** + * 判断当前网络类型(兼容 Android 16+,不依赖 API 29) + * @param context 上下文(建议用 Application 上下文,避免内存泄漏) + */ + public static NetworkType getCurrentNetworkType(Context context) { + // 获取 ConnectivityManager 系统服务 + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + + // Android 6.0+(API 23+):优先使用 NetworkCapabilities(更准确) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + NetworkCapabilities networkCapabilities = + connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork()); + + if (networkCapabilities != null) { + // 有线网络(TRANSPORT_ETHERNET) + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + return NetworkType.ETHERNET; + } + // WiFi 网络(TRANSPORT_WIFI) + else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return NetworkType.WIFI; + } + // 蜂窝网络(TRANSPORT_CELLULAR) + else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return NetworkType.CELLULAR; + } + // 其他未知网络(蓝牙、VPN 等) + else { + return NetworkType.UNKNOWN; + } + } else { + // 无活跃网络 + return NetworkType.NONE; + } + } + + // Android 5.0-(API <23):兼容旧 API(已废弃,仅作兜底) + @SuppressWarnings("deprecation") + NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); + if (networkInfo != null && networkInfo.isConnected()) { + switch (networkInfo.getType()) { + case ConnectivityManager.TYPE_WIFI: + return NetworkType.WIFI; + case ConnectivityManager.TYPE_ETHERNET: + return NetworkType.ETHERNET; + case ConnectivityManager.TYPE_MOBILE: + return NetworkType.CELLULAR; + default: + return NetworkType.UNKNOWN; + } + } else { + // 无网络连接 + return NetworkType.NONE; + } + } + + /** + * 辅助方法:判断是否为 WiFi 网络 + */ + public static boolean isWifi(Context context) { + return getCurrentNetworkType(context) == NetworkType.WIFI; + } + + /** + * 辅助方法:判断是否为有线网络 + */ + public static boolean isEthernet(Context context) { + return getCurrentNetworkType(context) == NetworkType.ETHERNET; + } + + /** + * 辅助方法:判断是否为蜂窝网络 + */ + public static boolean isCellular(Context context) { + return getCurrentNetworkType(context) == NetworkType.CELLULAR; + } + + /** + * 辅助方法:判断是否有网络连接(WiFi/有线/蜂窝均可) + */ + public static boolean isNetworkAvailable(Context context) { + return getCurrentNetworkType(context) != NetworkType.NONE; + } + + /** + * 细分蜂窝网络类型(2G/3G/4G,兼容低版本,无 5G 判断) + */ + public static String getCellularSubType(Context context) { + TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + + // 低版本统一使用旧 API(避免依赖高版本常量) + @SuppressWarnings("deprecation") + int networkType = telephonyManager.getNetworkType(); + + switch (networkType) { + // 4G 网络(LTE) + case TelephonyManager.NETWORK_TYPE_LTE: + return "4G"; + // 3G 网络(HSPA 系列) + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPAP: + case TelephonyManager.NETWORK_TYPE_UMTS: + return "3G"; + // 2G 网络(GSM/CDMA 等) + case TelephonyManager.NETWORK_TYPE_GSM: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_1xRTT: + return "2G"; + // 其他未知蜂窝网络 + default: + return "未知蜂窝网络"; + } + } + + /** + * 新增:获取当前连接的WiFi名称(SSID) + * @param context 上下文 + * @return WiFi名称(未连接则返回空字符串) + */ + public static String getWifiSsid(Context context) { + try { + // 获取WiFi管理器(使用Application上下文,避免内存泄漏) + WifiManager wifiManager = (WifiManager) context.getApplicationContext() + .getSystemService(Context.WIFI_SERVICE); + + if (wifiManager == null) { + return ""; // 设备不支持WiFi + } + + // 获取WiFi连接信息(低版本兼容) + @SuppressWarnings("deprecation") + android.net.wifi.WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + + if (wifiInfo == null) { + return ""; // 未连接WiFi + } + + String ssid = wifiInfo.getSSID(); + if (TextUtils.isEmpty(ssid)) { + return ""; + } + + // 处理SSID格式:部分设备返回带双引号(如"MyWiFi"),去除引号 + if (ssid.startsWith("\"") && ssid.endsWith("\"")) { + ssid = ssid.substring(1, ssid.length() - 1); + } + + // 排除无效SSID(如"") + return "unknown ssid".equalsIgnoreCase(ssid) ? "" : ssid; + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static String getActiveNetworkIp(Context context) { + // 优先通过ConnectivityManager判断活动网络类型 + String networkType = getActiveNetworkType(context); + + try { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface ni = interfaces.nextElement(); + + // 根据网络类型过滤接口 + if (!isValidInterface(ni, networkType)) continue; + + Enumeration addresses = ni.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (!addr.isLoopbackAddress() && addr instanceof Inet4Address) { + String ip = addr.getHostAddress(); + // 排除热点默认IP + if (!ip.startsWith("192.168.43.")) { + return ip; + } + } + } + } + } catch (SocketException e) { + e.printStackTrace(); + } + + return null; // 未找到有效IP + } + + // 判断活动网络类型 + private static String getActiveNetworkType(Context context) { + ConnectivityManager cm = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Network network = cm.getActiveNetwork(); + if (network == null) return "NONE"; + + NetworkCapabilities capabilities = cm.getNetworkCapabilities(network); + if (capabilities == null) return "NONE"; + + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return "WIFI"; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + return "ETHERNET"; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return "CELLULAR"; + } + } else { + // 兼容旧版本 + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + if (activeNetwork != null) { + if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { + return "WIFI"; + } else if (activeNetwork.getType() == ConnectivityManager.TYPE_ETHERNET) { + return "ETHERNET"; + } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { + return "CELLULAR"; + } + } + } + + return "NONE"; + } + + // 根据网络类型验证接口 + private static boolean isValidInterface(NetworkInterface ni, String networkType) { + try { + // 跳过未激活或回环接口 + if (!ni.isUp() || ni.isLoopback()) return false; + + switch (networkType) { + case "WIFI": + return ni.getName().contains("wlan"); + case "ETHERNET": + return ni.getName().startsWith("eth") || ni.getName().startsWith("usb"); + case "CELLULAR": + return ni.getName().contains("rmnet") || ni.getName().contains("ppp"); + default: + return true; // 未知类型不过滤 + } + } catch (SocketException e) { + e.printStackTrace(); + return false; + } + } +} \ No newline at end of file diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Prog.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Prog.java index ae2de6b..1990488 100644 --- a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Prog.java +++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Prog.java @@ -99,6 +99,7 @@ public class Prog extends AbsLayout { page.repeatTimes = pageMap.intg("repeatTimes", 1); page.parse(pageMap.jslist("schedules")); var waitAudio = pageMap.bool("waitAudio"); + var zzz = 0; HashMap videoMap = new HashMap<>(); for(int ll=layers.size()-1; ll>=0; ll--) { var layer = new Layer(); @@ -112,6 +113,7 @@ public class Prog extends AbsLayout { bdWidth = bdEle.img.getHeight(); } var src = new Source(); + src.z = zzz++; for(var source : sources) { src.type = source.stnn("_type"); if(src.type.isEmpty()) continue; @@ -271,10 +273,11 @@ public class Prog extends AbsLayout { src.box = source.bool("isPreSplit") ? this : box; layer.srcs.add(src); src = new Source(); + src.z = zzz++; } } else if(src.type.equals("Audio")) { if(id==null) continue; - src.typ = 'A'; + src.typ = 'V'; src.path = Util.programDir + "/" +id; src.vol = source.intg("vol", 100) / 100.0f; src.dur = dur; @@ -282,6 +285,7 @@ public class Prog extends AbsLayout { src.box = box; layer.srcs.add(src); src = new Source(); + src.z = zzz++; } else if(src.type.equals("Scroll")) { JSList imgs = source.jslist("imgs"); if(imgs.isEmpty()) continue; @@ -365,9 +369,11 @@ public class Prog extends AbsLayout { src.view = imgView; src.view.setVisibility(GONE); src.view.setLayoutParams(geo); + src.view.setZ(src.z); box.addView(src.view); layer.srcs.add(src); src = new Source(); + src.z = zzz++; } } } else if(src.type.equals("DigitalClock")) src.view = new SrcDigitalClock(this, source); @@ -438,9 +444,11 @@ public class Prog extends AbsLayout { if(src.view==null) continue; src.view.setVisibility(GONE); src.view.setLayoutParams(geo); + src.view.setZ(src.z); (source.bool("isPreSplit") ? this : box).addView(src.view); layer.srcs.add(src); src = new Source(); + src.z = zzz++; } if(bdEle!=null && bdStart < bdEnd) { JSList geometry = border.jslist("geometry"); @@ -449,6 +457,7 @@ public class Prog extends AbsLayout { src.rotate = (float) border.dbl("rotate"); src.view = bdEle; src.view.setVisibility(GONE); + src.view.setZ(src.z); box.addView(src.view, new AbsLayout.LayoutParams(geometry.get(0).intValue(), geometry.get(1).intValue(), geometry.get(2).intValue(), geometry.get(3).intValue())); layer.srcs.add(src); } @@ -516,7 +525,7 @@ public class Prog extends AbsLayout { AbsLayout box; String path; float vol; - int dur; + int dur, z; boolean useSW; public Source() { @@ -528,7 +537,8 @@ public class Prog extends AbsLayout { void show() { if(typ=='V') { if(alphaShow==1) return; - ((SrcVideo)view).ijkPlayer.start(); + var video = (SrcVideo) view; + video.ijkPlayer.start(); view.setAlpha((alphaShow = 1) * alpha); } else { if(view.getVisibility()==VISIBLE) return; @@ -538,7 +548,6 @@ public class Prog extends AbsLayout { if(isEntryRand) entryEff = Effect.values()[Util.rand.nextInt(Effect.values().length)]; if(isExitRand) exitEff = Effect.values()[Util.rand.nextInt(Effect.values().length)]; resetEff(); - doEff(); if(tts!=null) tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, null); } void hide() { @@ -554,17 +563,24 @@ public class Prog extends AbsLayout { if(view instanceof GifImageView) ((GifImageView) view).setImageURI(uri); if(tts!=null && tts.isSpeaking()) tts.stop(); } - void prepare(long seek) { - var ms = System.currentTimeMillis(); + void prepare(List shows, long seek) { if(view==null) { var video = new SrcVideo(box.getContext(), path, vol, dur, seek, useSW); video.setLayoutParams(geo); video.setAlpha(alphaShow = 0); video.setVisibility(VISIBLE); + video.setZ(z); box.addView(view = video); + shows.add(new MainActivity.ShowHide(startMilli+450, ()->{ + if(video.ijkPlayer==null) Util.println(" ijkPlayer null"); + else { + video.ijkPlayer.pause(); + video.ijkPlayer.seekTo(seek<1000 ? 0 : seek); + } + })); } - ms = System.currentTimeMillis() - ms; - Util.println(" prepare ms "+ms); + shows.add(new MainActivity.ShowHide(startMilli+500, this, 'S')); + isShow = true; } void release() { if(view instanceof SrcVideo) { @@ -574,6 +590,7 @@ public class Prog extends AbsLayout { } } void doEff() { + if(view==null) return; var w = view.getLayoutParams().width; var h = view.getLayoutParams().height; if(ff < entryDur) { @@ -754,11 +771,7 @@ public class Prog extends AbsLayout { for(var src : layer.srcs) { src.endMilli = start + src.endTime; src.startMilli = start + src.startTime; - if(src.startTime == 0) { - src.prepare(cur - src.startMilli); - shows.add(new MainActivity.ShowHide(src.startMilli+1000, src, 'S')); - src.isShow = true; - } + if(src.startTime == 0) src.prepare(shows, cur - src.startMilli); } } if(Util.logOn) { @@ -778,14 +791,11 @@ public class Prog extends AbsLayout { for(var src : layer.srcs) { if(src.isShow) { if(milli >= src.endMilli) { - shows.add(new MainActivity.ShowHide(src.endMilli+1000, src, 'H')); + shows.add(new MainActivity.ShowHide(src.endMilli+500, src, 'H')); src.isShow = false; } - else src.doEff(); } else if(milli < src.endMilli && milli >= src.startMilli) { - src.prepare(milli - src.startMilli); - shows.add(new MainActivity.ShowHide(src.startMilli+1000, src, 'S')); - src.isShow = true; + src.prepare(shows, milli - src.startMilli); } } if(milli >= layer.endMilli) { @@ -793,14 +803,7 @@ public class Prog extends AbsLayout { for(var src : layer.srcs) { src.endMilli += layer.dur; src.startMilli += layer.dur; - if(src.startTime > 0) { - shows.add(new MainActivity.ShowHide(src.startMilli+1000, src, 'H')); - src.isShow = false; - } else { - src.prepare(0); - shows.add(new MainActivity.ShowHide(src.startMilli+1000, src, 'S')); - src.isShow = true; - } + if(src.startTime == 0) src.prepare(shows, 0); } } } diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/SrcVideo.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/SrcVideo.java index 265c355..1fc452d 100644 --- a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/SrcVideo.java +++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/SrcVideo.java @@ -23,7 +23,6 @@ public class SrcVideo extends TextureView implements TextureView.SurfaceTextureL super(context); this.vol = vol; setSurfaceTextureListener(this); - Util.println(" video new"); ijkPlayer = new IjkMediaPlayer(); if(! useSW) { ijkPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-avc", 1); @@ -41,7 +40,10 @@ public class SrcVideo extends TextureView implements TextureView.SurfaceTextureL ijkPlayer.setOnPreparedListener(null); if(getAlpha() < 0.25) { ijkPlayer.pause(); - ijkPlayer.seekTo(seek<500 ? 0 : seek); + var aaa = seek<1000 ? 0 : seek; + ijkPlayer.seekTo(aaa); + ijkPlayer.pause(); + ijkPlayer.seekTo(aaa); } else if(seek>=1000) ijkPlayer.seekTo(seek); bitRate = ijkPlayer.getBitRate(); var diff = dur - ijkPlayer.getDuration(); @@ -77,26 +79,13 @@ public class SrcVideo extends TextureView implements TextureView.SurfaceTextureL public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {} void release() { - Util.println(" video releasing"); if(ijkPlayer!=null) { ijkPlayer.release(); ijkPlayer = null; } - Util.println(" surface releasing"); if(surface!=null) { surface.release(); surface = null; } - Util.println(" surface released"); } - -// @Override -// public void onVisibilityAggregated(boolean isVisible) { -// super.onVisibilityAggregated(isVisible); -// if(isVisible) ijkPlayer.start(); -// else { -// ijkPlayer.pause(); -// ijkPlayer.seekTo(0); -// } -// } } \ No newline at end of file diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/SrcWeb.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/SrcWeb.java index f9b9b59..2af558f 100644 --- a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/SrcWeb.java +++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/SrcWeb.java @@ -66,7 +66,7 @@ public class SrcWeb extends WebView implements Choreographer.FrameCallback { }); loadUrl(url); refresh = json.intg("refreshSec")*1000; - if(refresh==0 || Util.custom!=Util.Custom.Yishi) this.calls = prog.calls; + if(refresh==0 && Util.custom!=Util.Custom.Yishi) this.calls = prog.calls; prog.calls.add(this); nextMs = System.currentTimeMillis() + refresh; } @@ -111,8 +111,10 @@ public class SrcWeb extends WebView implements Choreographer.FrameCallback { loadUrl(url); Util.println(" WebView Refreshed"); } else if(calls!=null) { - calls.remove(this); - calls = null; + var calls = this.calls; + this.calls = null; + var acti = MainActivity.ins; + if(acti!=null) acti.showHides.add(new MainActivity.ShowHide(0, () -> calls.remove(this))); } } } diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/TCPThread.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/TCPThread.java index 1c9c4f1..24e91d1 100644 --- a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/TCPThread.java +++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/TCPThread.java @@ -141,6 +141,8 @@ public class TCPThread extends Thread { else { var fOut = new FileOutputStream(Util.programDir + "/program"); fOut.write(json); + var spaces = " ".getBytes(); + for(int i=0;i<1000; i++) fOut.write(spaces); fOut.flush(); fOut.getFD().sync(); fOut.close(); @@ -191,6 +193,8 @@ public class TCPThread extends Thread { else { var fOut = new FileOutputStream(Util.programDir + "/program"); fOut.write(json); + fOut.write("{}".getBytes()); + var spaces = " ".getBytes(); fOut.flush(); fOut.getFD().sync(); fOut.close(); @@ -275,7 +279,6 @@ public class TCPThread extends Thread { writer.append(" Size Avas: ").append(String.valueOf(main.avas.size())).append(" Pages: ").append(main.progView==null ? "null" : String.valueOf(main.progView.pages.size())); writer.append("\n"); writer.append(" Launch: ").append(fmt.format(main.launchMilli)).append("\n"); - writer.append(" Sync: ").append(fmt.format(main.syncMs)).append("\n"); if(main.insView==null) writer.append(" InseView is Null\n"); if(main.progView==null) writer.append(" ProgView is Null\n"); if(main.avas.isEmpty()) writer.append(" No Avas\n"); @@ -309,6 +312,8 @@ public class TCPThread extends Thread { writer.append("\n\n"); writer.append(Util.cfg.toStr()); writer.append("\n\n"); + writer.append("showeds "+main.showeds.size()).append(" showHides "+main.showHides.size()); + writer.append("\n\n"); var latch = new CountDownLatch(1); if(main!=null) main.runOnUiThread(() -> { @@ -376,7 +381,7 @@ public class TCPThread extends Thread { var inse = new File(Util.programDir+"/insert"); var files = new File(Util.programDir).listFiles(); if(inse.isFile()) { - var json = IOs.readStrClose(new FileInputStream(inse)); + var json = IOs.readStrClose(new FileInputStream(inse)).trim(); if(files!=null) for(var file : files) if(! "program".equals(file.getName())) json = json.replace("\""+file.getName()+"\"", "\""+file.getName()+"\"/*"+file.length()+" "+fmt.format(new Date(file.lastModified()))+"*/"); var writer = new OutputStreamWriter(out); writer.append("insert:\n"); @@ -387,7 +392,7 @@ public class TCPThread extends Thread { var prog = new File(Util.programDir+"/program"); if(! prog.isFile()) new JSMap("msg", "'program' file not exist").write(out); else { - var json = IOs.readStrClose(new FileInputStream(prog)); + var json = IOs.readStrClose(new FileInputStream(prog)).trim(); if(files!=null) for(var file : files) if(! "program".equals(file.getName())) json = json.replace("\""+file.getName()+"\"", "\""+file.getName()+"\"/*"+file.length()+" "+fmt.format(new Date(file.lastModified()))+"*/"); var writer = new OutputStreamWriter(out); writer.append("\nprogram:\n"); diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Util.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Util.java index 42a7d06..7bd59f9 100644 --- a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Util.java +++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Util.java @@ -43,6 +43,7 @@ public class Util { public static volatile long downId; public static int screenWidth = 1920, screenHeight = 1080; public static double lat, lng; + public static char cardType = 0; public static boolean isScreenOn, isAudioGain, logOn; public static void initDir(Context ctx) {