package com.xixun.xixunplayer; import static android.view.View.VISIBLE; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.graphics.BitmapFactory; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.os.StatFs; import android.os.StrictMode; import android.view.Choreographer; import android.view.View; import android.webkit.WebView; import androidx.activity.ComponentActivity; import androidx.annotation.NonNull; import androidx.annotation.OptIn; import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.media3.common.util.UnstableApi; import com.xixun.joey.aidlset.CardService; import com.xixun.xy.conn.aidl.ConnService; import net.lingala.zip4j.ZipFile; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.HashSet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import gnph.util.Chsets; import gnph.util.IOs; import gnph.util.JSList; import gnph.util.JSMap; import gnph.util.NumFmts; import gnph.util.O; public class MainActivity extends ComponentActivity implements Choreographer.FrameCallback, Runnable { public static MainActivity ins; public Intent environIntent = new Intent(); HashSet environs = new HashSet<>(); BackView backView; Prog progView; long launchMilli = System.currentTimeMillis(); long syncMs; int state; @RequiresApi(api = Build.VERSION_CODES.R) @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); var msg = "==== MainActivity onCreate ==== UI Thread: "+Thread.currentThread().getId(); Util.println(msg); ins = this; startService(new Intent(this, RestartService.class)); StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build()); if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) init(); else { Util.println("---- No permission, Try again ..."); ActivityCompat.requestPermissions(this, new String[]{ android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, android.Manifest.permission.RECEIVE_BOOT_COMPLETED, android.Manifest.permission.INTERNET }, 999); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if(requestCode==999 && grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED && backView==null) init(); } int receCnt; ConnService connService; @SuppressLint("UnspecifiedRegisterReceiverFlag") public void init() { var dir = Build.VERSION.SDK_INT < Build.VERSION_CODES.R ? Environment.getExternalStorageDirectory().getAbsolutePath() + "/XixunPlayer" : getExternalFilesDir(null).getAbsolutePath(); var msg = "---- dir "+dir; Util.println(msg); Util.programDir = dir + "/program"; Util.backImgFile = dir + "/background"; var program = new File(Util.programDir); if(program.isFile()) program.delete(); msg = "---- mkdir: "+program.mkdirs(); Util.println(msg); var cardConn = new ServiceConnection() { public void onServiceDisconnected(ComponentName name) { Util.println("Disconnected cardsystem aidl service"); } public void onServiceConnected(ComponentName name, IBinder iBinder) { unbindService(this); Util.println("Bind cardsystem aidl service success"); var service = CardService.Stub.asInterface(iBinder); try { Util.isScreenOn = service.isScreenOpen(); Util.screenWidth = service.getScreenWidth(); Util.screenHeight = service.getScreenHeight(); Util.println(" IsScreenOn: "+Util.isScreenOn+" screen: "+Util.screenWidth+" x "+Util.screenHeight); if(receCnt++>=1) { backView = new BackView(MainActivity.this, Util.screenWidth, Util.screenHeight); state = 5; if(Util.isScreenOn) initProg(); else state = 8; if(progView==null) setContentView(backView); } } catch (Exception e) { Util.makeText(MainActivity.this, Util.toStr(e)).show(); Util.printStackTrace(e); } } }; var intent = new Intent("com.xixun.joey.aidlset.SettingsService"); intent.setPackage("com.xixun.joey.cardsystem"); bindService(intent, cardConn, Context.BIND_AUTO_CREATE); var connConn = new ServiceConnection() { public void onServiceDisconnected(ComponentName name) { Util.println("Disconnected xy.conn aidl service"); connService = null; } public void onServiceConnected(ComponentName name, IBinder iBinder) { unbindService(this); Util.println("Bind xy.conn aidl service success"); connService = ConnService.Stub.asInterface(iBinder); try { Util.serverURL = connService.getServerURL(); Util.println(" ServerURL: "+Util.serverURL); if(Util.serverURL==null || Util.serverURL.isEmpty()) Util.serverURL = "https://m2mled.net/"; else { if(! Util.serverURL.startsWith("http")) Util.serverURL = "http://"+Util.serverURL; if(! Util.serverURL.endsWith("/")) Util.serverURL += "/"; } if(receCnt++>=1) { backView = new BackView(MainActivity.this, Util.screenWidth, Util.screenHeight); state = 5; if(Util.isScreenOn) initProg(); else state = 8; if(progView==null) setContentView(backView); } } catch (Exception e) { Util.makeText(MainActivity.this, Util.toStr(e)).show(); Util.printStackTrace(e); } } }; intent = new Intent("xixun.intent.action.CONNECTION_INFO"); intent.setPackage("com.xixun.xy.conn"); bindService(intent, connConn, Context.BIND_AUTO_CREATE); registerReceiver(new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { Util.println("Receive PAUSE_PLAYER"); Util.isScreenOn = ! intent.getBooleanExtra("pause", false); Util.println(" IsScreenOn: "+Util.isScreenOn); if(! Util.isScreenOn) { state = 8; if(progView!=null) { progView.release(); progView = null; setContentView(backView); } } else if(progView==null) initProg(); } }, new IntentFilter("com.xixun.action.PAUSE_PLAYER")); registerReceiver(new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { Util.println("Receive CHANGE_COMPANYID"); if(connService!=null) { try { Util.serverURL = connService.getServerURL(); Util.println(" ServerURL: "+Util.serverURL); } catch (Exception e) { Util.makeText(MainActivity.this, Util.toStr(e)).show(); Util.printStackTrace(e); } } } }, new IntentFilter("com.xixun.joey.CHANGE_COMPANYID")); registerReceiver(new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { MainActivity.this.environIntent = intent; for(var environ : environs) { environ.onReceive(intent); ((View)environ).invalidate(); } } }, new IntentFilter("xixun.intent.action.TEMPERATURE_HUMIDITY")); registerReceiver(new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { try { var code = intent.getIntExtra("code", 0); Util.println(" remote_control "+code); if(progView == null || code > progView.pages.size()) return; if(! progView.avas.isEmpty()) page(progView.curAva).hide(); var millis = (System.currentTimeMillis()+999)/1000*1000; if(code > 0) { progView.avas.clear(); progView.avas.add(code-1); progView.curAva = 0; progView.curTimes = 1; progView.waitTo = 0; //点播 var page = page(0); page.setMillis(millis); if(state != 6) { setContentView(progView); state = 6; } } else { progView.waitTo = Long.MAX_VALUE; syncProg(millis); } } catch (Throwable e) { Util.makeText(MainActivity.this, Util.toStr(e)).show(); Util.printStackTrace(e); } } }, new IntentFilter("com.xixun.yzd.REMOTE_CONTROL")); var intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); intentFilter.addDataScheme("file"); registerReceiver(new BroadcastReceiver(){ long lastMs; final char[] pass = {'8','8','8'}; @Override public void onReceive(Context context, Intent intent) { var path = intent.getData().getPath(); Util.println("\nMEDIA_MOUNTED path: "+path); var ms = System.currentTimeMillis(); if(ms-lastMs<1000) return; lastMs = ms; Util.makeText(MainActivity.this, "MEDIA_MOUNTED path: "+path).show(); new Thread(()->{ try { var zip = new ZipFile(path+"/program.zip"); if(zip.isEncrypted()) zip.setPassword(pass); long size = 0; ByteArrayOutputStream progJson = null; var headers = zip.getFileHeaders(); for(var header : headers) { size += header.getUncompressedSize(); if("program".equals(header.getFileName())) { progJson = new ByteArrayOutputStream(); IOs.writeClose(progJson, zip.getInputStream(header)); } } if(progJson==null) { Util.println("No program File"); runOnUiThread(() -> Util.makeText(MainActivity.this, "No program File").show()); return; } if(size==0) { Util.println("zip size is 0"); runOnUiThread(() -> Util.makeText(MainActivity.this, "zip size is 0").show()); return; } Util.deleteFiles(size, null); for(var header : headers) if(! "program".equals(header.getFileName())) zip.extractFile(header, Util.programDir); var json = progJson.toByteArray(); runOnUiThread(() -> { Util.println("Import Succeed"); Util.makeText(MainActivity.this, "Import Succeed").show(); initProg(json); }); } catch (Exception e) { Util.printStackTrace(e); runOnUiThread(() -> Util.makeText(MainActivity.this, Util.toStr(e)).show()); } }).start(); } }, intentFilter); new Thread(this).start(); } public void stopProg() { if(progView==null) return; progView.release(); progView = null; setContentView(backView); } public boolean delProgFile() { stopProg(); var files = new File(Util.programDir).listFiles(); var ok = true; 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(); } catch (Throwable ignored) { } return ok; } public void initProg() { try { var task = JSMap.fromClose(new BufferedInputStream(new FileInputStream(Util.programDir + "/program"))).jsmap("task"); if(task==null) { state = 3; return; } var view = new Prog(task, this); if(view.getChildCount()==0) { state = 3; return; } if(progView!=null) progView.release(); progView = view; setContentView(progView); state = 5; Util.println("Init Sync"); syncProg((System.currentTimeMillis()+999)/1000*1000); if(canAdd) { choreographer.postFrameCallback(this); canAdd = false; } } catch (FileNotFoundException e) { state = 3; Util.println(""+e); } catch (Throwable e) { state = 7; Util.makeText(this, Util.toStr(e)).show(); Util.printStackTrace(e); } } public void initProg(byte[] json) { try { Util.println("\nParse Prog Json"); var root = JSMap.from(json); var task = root.jsmap("task"); if(task==null) { if(! root.containsKey("layers")) { state = 7; Util.println(" Error: task==null\n"); Util.println(new String(json, Chsets.UTF8)); return; } task = new JSMap("items", new JSList<>(new JSMap("repeatTimes", 1, "_program", root))); } var view = new Prog(task, this); if(view.getChildCount()==0) { state = 7; Util.println(" Error: ChildCount==0\n"); Util.println(new String(json, Chsets.UTF8)); return; } if(progView!=null) progView.release(); progView = view; setContentView(progView); var fOut = new FileOutputStream(Util.programDir + "/program"); fOut.write(json); fOut.flush(); fOut.getFD().sync(); fOut.close(); state = 5; Util.println("Init Sync"); syncProg((System.currentTimeMillis()+999)/1000*1000); if(canAdd) { choreographer.postFrameCallback(this); canAdd = false; } } catch (Throwable e) { state = 7; Util.makeText(this, Util.toStr(e)).show(); Util.printStackTrace(e); Util.println(new String(json, Chsets.UTF8)); } } Choreographer choreographer = Choreographer.getInstance(); boolean canAdd = true; @Override public void doFrame(long frameTimeNanos) { if(progView == null) { canAdd = true; return; } var milli = System.currentTimeMillis(); if(progView.avas.isEmpty()) { if(milli >= progView.waitTo) { progView.waitTo = Long.MAX_VALUE; Util.println("wait sync"); syncProg(milli); } } else { var lastPage = page(progView.curAva); if(milli >= lastPage.endMilli) { lastPage.hide(); if(progView.waitTo > 0) { //waitTo==0 为点播,不换页 if(progView.curTimes < lastPage.repeatTimes) progView.curTimes++; else { progView.curTimes = 1; progView.curAva++; if(progView.curAva >= progView.avas.size()) { var isDiff = milli-lastPage.endMilli>=1000; if(Util.buf.length()>1000000) Util.buf.replace(0, 100000, ""); Util.println("isDiff: "+isDiff+" endMs: "+lastPage.endMilli+" millis:"+milli); syncProg(isDiff ? milli : lastPage.endMilli); if(progView.avas.isEmpty()) Util.println("after. No Avas"); else Util.println("after. curAva: "+progView.curAva+" endMs: "+page(progView.curAva).endMilli); choreographer.postFrameCallback(this); canAdd = false; for(var call : progView.calls) call.doFrame(milli); return; } } } page(progView.curAva).setMillis(lastPage.endMilli); Util.println("curAva: "+progView.curAva+" endMs: "+page(progView.curAva).endMilli); } else { for(var layer : lastPage.layers) { for(var src : layer.srcs) { if(src.view.getVisibility()==VISIBLE) { if(milli >= src.endMilli) src.hide(); else src.doEff(); } else if(milli < src.endMilli && milli >= src.startMilli) src.show(); } if(milli >= layer.endMilli) { layer.endMilli += layer.dur; for(var src : layer.srcs) { src.endMilli += layer.dur; src.startMilli += layer.dur; if(src.startTime > 0) src.hide(); else src.show(); } } } } } choreographer.postFrameCallback(this); canAdd = false; for(var call : progView.calls) call.doFrame(milli); } void syncProg(long milli) { progView.curTimes = 1; progView.avas.clear(); var dur = 0; for(int i=0; i hases = null; ByteArrayOutputStream progJson = null; while(true) { var obj = JSMap.from(in); var _type = obj.stnn("_type"); Util.println("_type: "+_type); if("consult".equals(_type)) { JSList ids = obj.jslist("idList"); hases = new HashSet<>(); if(ids!=null) for(int i=0; i { backView.cosImg = BitmapFactory.decodeFile(Util.backImgFile); backView.invalidate(); }); } else if("imgFileEnd".equals(_type)) { new JSMap("success", true).write(out); } else if("proEnd".equals(_type)) { new JSMap("success", progJson!=null).write(out); if(progJson!=null) { var json = progJson.toByteArray(); progJson = null; runOnUiThread(() -> initProg(json)); } } else if("DelPrograms".equals(_type)) { var latch = new CountDownLatch(1); var ok = new AtomicBoolean(false); runOnUiThread(() -> { ok.set(delProgFile()); latch.countDown(); }); try { latch.await(); } catch (InterruptedException ignored) {} new JSMap("success", ok.get()).write(out); } else if("DelBackImg".equals(_type)) { MainActivity.ins.runOnUiThread(() -> { MainActivity.ins.backView.cosImg = null; MainActivity.ins.backView.invalidate(); }); new JSMap("success", new File(Util.backImgFile).delete()).write(out); } else if("getPlayerState".equals(_type)) { var descs = Util.getState(state); new JSMap( "code", state, "des_en", descs[0], "des", descs[1] ).write(out); } else if("GetInfo".equals(_type)) { var writer = new OutputStreamWriter(out); var Fmt = new SimpleDateFormat("yy-MM-dd HH:mm:ss.SSS"); var dur = 0; if(progView!=null) for(var page : progView.pages) dur += page.tDur; writer.append("ProgSend: ").append(Fmt.format(new File(Util.programDir + "/program").lastModified())).append(" ProgDur: ").append(String.valueOf(dur)); if(progView!=null) writer.append(" Pages: ").append(String.valueOf(progView.avas.size())).append(" / ").append(String.valueOf(progView.pages.size())); writer.append("\n"); writer.append(" Launch: ").append(Fmt.format(launchMilli)).append("\n"); writer.append(" Sync: ").append(Fmt.format(syncMs)).append("\n"); writer.append("Page End: ").append(progView==null || progView.avas.isEmpty() ? "0" : Fmt.format(page(progView.curAva).endMilli)).append(" CurPage: ").append(String.valueOf(progView.curAva)).append(" / ").append(progView==null || progView.avas.isEmpty() ? "0" : String.valueOf(progView.avas.get(progView.curAva))).append("\n"); writer.append(" Current: ").append(Fmt.format(System.currentTimeMillis())).append("\n"); var statFs = new StatFs(Environment.getExternalStorageDirectory().getPath()); writer.append(" Disk: ").append(String.valueOf(statFs.getTotalBytes()/1000000)).append(" ").append(String.valueOf(statFs.getAvailableBytes()/1000000)).append(" ").append(String.valueOf(statFs.getFreeBytes()/1000000)).append(" (total avail free)\n"); var actManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); var memoryInfo = new ActivityManager.MemoryInfo(); actManager.getMemoryInfo(memoryInfo); writer.append(" Memory: ").append(String.valueOf(memoryInfo.totalMem/1000000)).append(" ").append(String.valueOf(memoryInfo.availMem/1000000)).append(" ").append(String.valueOf(memoryInfo.threshold/1000000)).append(" ").append(String.valueOf(memoryInfo.lowMemory)).append(" (total avail threshold low)\n"); var runtime = Runtime.getRuntime(); writer.append("Runtime: ").append(String.valueOf(runtime.maxMemory()/1000000)).append(" ").append(String.valueOf(runtime.totalMemory()/1000000)).append(" ").append(NumFmts.cfix2().format(runtime.freeMemory()*0.000001)).append(" (max total free)\n"); writer.append("/proc/stat\n"); IOs.writeCloseIn(writer, new FileReader("/proc/stat")); writer.append("\n"); var latch = new CountDownLatch(1); runOnUiThread(() -> { if(progView!=null && ! progView.avas.isEmpty()) { var page = page(progView.curAva); for(var layer : page.layers) for(var src : layer.srcs) if(src.view.getVisibility()==VISIBLE) { try { if(src.view instanceof SrcVideo) { var view = (SrcVideo) src.view; if(view.ijkPlayer!=null) { writer.append("VideoPlaying: ").append(String.valueOf(view.ijkPlayer.isPlaying())).append("\tCur/Dur: ").append(String.valueOf(view.ijkPlayer.getCurrentPosition())).append("/").append(String.valueOf(view.ijkPlayer.getDuration())).append("\n"); var mediaInfo = view.ijkPlayer.getMediaInfo(); var vStream = mediaInfo.mMeta.mVideoStream; writer.append(" BitRate: ").append(String.valueOf(view.ijkPlayer.getBitRate()/1000)).append("k\tFPS: ").append(String.valueOf(vStream.mFpsNum/(float)vStream.mFpsDen)).append(" (").append(String.valueOf(vStream.mFpsNum)).append("/").append(String.valueOf(vStream.mFpsDen)).append(")\n"); var tracks = view.ijkPlayer.getTrackInfo(); for(var track : tracks) writer.append(" ").append(track.getInfoInline()).append("\n"); writer.append(" Format: ").append(mediaInfo.mMeta.mFormat).append("\n"); writer.append("VideoDecoder: ").append(mediaInfo.mVideoDecoder).append(" ").append(mediaInfo.mVideoDecoderImpl).append(" (").append(String.valueOf(view.ijkPlayer.getVideoDecoder())).append(")\n"); writer.append("AudioDecoder: ").append(mediaInfo.mAudioDecoder).append(" ").append(mediaInfo.mAudioDecoderImpl).append("\n"); //writer.append("PROFILE: ").append(mediaInfo.mMeta.getString(IjkMediaMeta.IJKM_KEY_CODEC_PROFILE)).append("\n"); } else if(view.exoPlayer!=null) { writer.append("VideoPlaying: ").append(String.valueOf(view.exoPlayer.isPlaying())).append(" ").append(String.valueOf(view.exoPlayer.getCurrentPosition())).append("/").append(String.valueOf(view.exoPlayer.getDuration())).append("\n"); writer.append(" BitRate: ").append(String.valueOf(view.bitRate/1000)).append("k\n"); writer.append("PlaybackStat: ").append(view.getState()).append("\n"); writer.append(" PlayerError: ").append(String.valueOf(view.exoPlayer.getPlayerError())).append("\n"); writer.append(" VideoFormat: ").append(String.valueOf(view.exoPlayer.getVideoFormat())).append("\n"); var cnt = view.exoPlayer.getRendererCount(); for(int rr=0; rr (int) (f2.lastModified() - f1.lastModified())); var writer = new OutputStreamWriter(out); for(var file : files) writer.append(file.getName()).append(' ').append(String.valueOf(file.length())).append('\n'); writer.append('\n'); writer.flush(); } else if("GetFile".equals(_type)) { var name = obj.str("name"); if(name!=null) IOs.writeCloseIn(out, new FileInputStream(Util.programDir+"/"+name)); else new JSMap("code", 1, "msg", "name is null").write(out); } out.flush(); Util.println("cmd end"); } } catch (Throwable e) { var emsg = e.getMessage(); if(emsg!=null && ("Socket closed".equals(emsg) || emsg.endsWith("end-of-input"))) { Util.println(emsg); continue; } MainActivity.ins.runOnUiThread(() -> Util.makeText(MainActivity.this, Util.toStr(e)).show()); Util.printStackTrace(e); } finally { O.close(in, out); Util.println("conn end\n"); } } } catch (Throwable e) { MainActivity.ins.runOnUiThread(() -> Util.makeText(MainActivity.this, Util.toStr(e)).show()); Util.printStackTrace(e); } } }