diff --git a/XixunPlayer-laun/.gitignore b/XixunPlayer-laun/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/XixunPlayer-laun/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/XixunPlayer-laun/.idea/.gitignore b/XixunPlayer-laun/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/XixunPlayer-laun/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/XixunPlayer-laun/.idea/.name b/XixunPlayer-laun/.idea/.name
new file mode 100644
index 0000000..d3f190d
--- /dev/null
+++ b/XixunPlayer-laun/.idea/.name
@@ -0,0 +1 @@
+XixunPlayer
\ No newline at end of file
diff --git a/XixunPlayer-laun/.idea/compiler.xml b/XixunPlayer-laun/.idea/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/XixunPlayer-laun/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer-laun/.idea/gradle.xml b/XixunPlayer-laun/.idea/gradle.xml
new file mode 100644
index 0000000..ae388c2
--- /dev/null
+++ b/XixunPlayer-laun/.idea/gradle.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer-laun/.idea/misc.xml b/XixunPlayer-laun/.idea/misc.xml
new file mode 100644
index 0000000..a25e2a9
--- /dev/null
+++ b/XixunPlayer-laun/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer-laun/.idea/vcs.xml b/XixunPlayer-laun/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/XixunPlayer-laun/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/.gitignore b/XixunPlayer-laun/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/XixunPlayer-laun/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/build.gradle b/XixunPlayer-laun/app/build.gradle
new file mode 100644
index 0000000..5a9afec
--- /dev/null
+++ b/XixunPlayer-laun/app/build.gradle
@@ -0,0 +1,62 @@
+plugins {
+ id 'com.android.application'
+}
+
+android {
+ namespace 'com.xixun.xixunplayer'
+ compileSdk 34
+
+ defaultConfig {
+ applicationId "com.xixun.xixunplayer"
+ minSdk 21
+ targetSdk 34
+ versionCode 1
+ versionName "2.1.29-laun-NTimes"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ buildFeatures {
+ aidl true
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'androidx.media3:media3-exoplayer:1.2.0'
+// implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
+// implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'
+ implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.25'
+ implementation files('libs/gnph.jar')
+ implementation files('libs/zip4j-2.10.0.jar')
+ implementation files('libs/xixun_card_settings_1.2.4.jar')
+ implementation files('libs/ijkplayer-java-0.8.8.aar')
+ implementation files('libs/ijkplayer-armv7a-0.8.8.aar')
+ implementation files('libs/ijkplayer-arm64-0.8.8.aar')
+ implementation files('libs/connService2.jar')
+}
+
+def getAppName() {
+ def stringsFile = android.sourceSets.main.res.sourceFiles.find { it.name.equals 'strings.xml' }
+ String s = new XmlParser().parse(stringsFile).string.find { it.@name.equals 'app_name' }.text();
+ return s.replaceAll("\"", "");
+}
+
+// 修改 Apk 名
+android.applicationVariants.configureEach { variant ->
+ variant.outputs.configureEach {
+ def fileName = "${getAppName()}-${versionName}.apk"
+ outputFileName = fileName
+ }
+}
diff --git a/XixunPlayer-laun/app/libs/connService2.jar b/XixunPlayer-laun/app/libs/connService2.jar
new file mode 100644
index 0000000..9bb1491
Binary files /dev/null and b/XixunPlayer-laun/app/libs/connService2.jar differ
diff --git a/XixunPlayer-laun/app/libs/gnph.jar b/XixunPlayer-laun/app/libs/gnph.jar
new file mode 100644
index 0000000..49484bf
Binary files /dev/null and b/XixunPlayer-laun/app/libs/gnph.jar differ
diff --git a/XixunPlayer-laun/app/libs/ijkplayer-arm64-0.8.8.aar b/XixunPlayer-laun/app/libs/ijkplayer-arm64-0.8.8.aar
new file mode 100644
index 0000000..105f873
Binary files /dev/null and b/XixunPlayer-laun/app/libs/ijkplayer-arm64-0.8.8.aar differ
diff --git a/XixunPlayer-laun/app/libs/ijkplayer-armv7a-0.8.8.aar b/XixunPlayer-laun/app/libs/ijkplayer-armv7a-0.8.8.aar
new file mode 100644
index 0000000..d84b81a
Binary files /dev/null and b/XixunPlayer-laun/app/libs/ijkplayer-armv7a-0.8.8.aar differ
diff --git a/XixunPlayer-laun/app/libs/ijkplayer-java-0.8.8.aar b/XixunPlayer-laun/app/libs/ijkplayer-java-0.8.8.aar
new file mode 100644
index 0000000..8224130
Binary files /dev/null and b/XixunPlayer-laun/app/libs/ijkplayer-java-0.8.8.aar differ
diff --git a/XixunPlayer-laun/app/libs/xixun_card_settings_1.2.4.jar b/XixunPlayer-laun/app/libs/xixun_card_settings_1.2.4.jar
new file mode 100644
index 0000000..0d17c22
Binary files /dev/null and b/XixunPlayer-laun/app/libs/xixun_card_settings_1.2.4.jar differ
diff --git a/XixunPlayer-laun/app/libs/zip4j-2.10.0.jar b/XixunPlayer-laun/app/libs/zip4j-2.10.0.jar
new file mode 100644
index 0000000..d80d844
Binary files /dev/null and b/XixunPlayer-laun/app/libs/zip4j-2.10.0.jar differ
diff --git a/XixunPlayer-laun/app/proguard-rules.pro b/XixunPlayer-laun/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/XixunPlayer-laun/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/src/main/AndroidManifest.xml b/XixunPlayer-laun/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..527532f
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/AndroidManifest.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/src/main/aidl/com/xixun/util/PlayerInfo.aidl b/XixunPlayer-laun/app/src/main/aidl/com/xixun/util/PlayerInfo.aidl
new file mode 100644
index 0000000..7726d8e
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/aidl/com/xixun/util/PlayerInfo.aidl
@@ -0,0 +1,47 @@
+// PlayerInfo.aidl
+package com.xixun.util;
+
+// Declare any non-default types here with import statements
+
+interface PlayerInfo {
+ //***需要实现,用于外部接口获取当前播放的节目名
+ String getProgramName();
+ //不需要实现
+ String getVersion();
+ //外部接口通知播放器屏幕宽高发生变化
+ void setScreenWidth(int w);
+ void setScreenHeight(int h);
+ //不需要
+ void taskScreenshot(String cmdId);
+ //不需要
+ void setExternalTemperature(float t);
+ void setInternalTemperature(float t);
+ void setHumidity(float h);
+ //暂时不需要
+ boolean forcePlayProgram(String pid);
+ boolean finishForcePlay();
+ //需要实现,让其他进程获取到当前播放的节目id
+ String getCurProgramId();
+ //需要,外部进程设置播放器的USB节目解压密码
+ void setUSBProgramPwd(String pwd);
+ //***需要,接收平台节目接口
+ String executeJosnCommand(String josn);
+ //暂停播放,以前的版本没有实现
+ void pausePlayer(boolean b);
+ //查询节目是否暂停播放
+ boolean isPause();
+ //***需要,清空节目和下载的素材
+ boolean clearTasks();
+ //需要,返回当前已有的节目数量
+ int countOfPrograms(int type);
+ //需要,指定id播放插播节目
+ void playInsertTask(String pid);
+ //需要,指定id停止播放插播节目
+ void stopInsertTask(String pid);
+ //***需要,回读当前节目json
+ String getProgramTask();
+ //不需要
+ void setUploadLogUrl(String playLog);
+ //不需要
+ String getUploadLogUrl();
+}
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W0.png b/XixunPlayer-laun/app/src/main/assets/imgs/W0.png
new file mode 100644
index 0000000..3e37d21
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W0.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W1.png b/XixunPlayer-laun/app/src/main/assets/imgs/W1.png
new file mode 100644
index 0000000..5dee862
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W1.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W10.png b/XixunPlayer-laun/app/src/main/assets/imgs/W10.png
new file mode 100644
index 0000000..7d55efe
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W10.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W13.png b/XixunPlayer-laun/app/src/main/assets/imgs/W13.png
new file mode 100644
index 0000000..8577bdc
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W13.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W14.png b/XixunPlayer-laun/app/src/main/assets/imgs/W14.png
new file mode 100644
index 0000000..2537239
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W14.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W15.png b/XixunPlayer-laun/app/src/main/assets/imgs/W15.png
new file mode 100644
index 0000000..4be21b3
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W15.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W16.png b/XixunPlayer-laun/app/src/main/assets/imgs/W16.png
new file mode 100644
index 0000000..0f2e015
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W16.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W17.png b/XixunPlayer-laun/app/src/main/assets/imgs/W17.png
new file mode 100644
index 0000000..04f83db
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W17.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W18.png b/XixunPlayer-laun/app/src/main/assets/imgs/W18.png
new file mode 100644
index 0000000..a3aec72
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W18.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W19.png b/XixunPlayer-laun/app/src/main/assets/imgs/W19.png
new file mode 100644
index 0000000..32736e2
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W19.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W2.png b/XixunPlayer-laun/app/src/main/assets/imgs/W2.png
new file mode 100644
index 0000000..c27cea2
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W2.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W20.png b/XixunPlayer-laun/app/src/main/assets/imgs/W20.png
new file mode 100644
index 0000000..2b326a8
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W20.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W29.png b/XixunPlayer-laun/app/src/main/assets/imgs/W29.png
new file mode 100644
index 0000000..1178e2e
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W29.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W3.png b/XixunPlayer-laun/app/src/main/assets/imgs/W3.png
new file mode 100644
index 0000000..10e825b
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W3.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W30.png b/XixunPlayer-laun/app/src/main/assets/imgs/W30.png
new file mode 100644
index 0000000..de3615c
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W30.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W31.png b/XixunPlayer-laun/app/src/main/assets/imgs/W31.png
new file mode 100644
index 0000000..b4dd147
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W31.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W32.png b/XixunPlayer-laun/app/src/main/assets/imgs/W32.png
new file mode 100644
index 0000000..581663d
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W32.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W33.png b/XixunPlayer-laun/app/src/main/assets/imgs/W33.png
new file mode 100644
index 0000000..3b51e5a
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W33.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W34.png b/XixunPlayer-laun/app/src/main/assets/imgs/W34.png
new file mode 100644
index 0000000..0bc4e72
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W34.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W35.png b/XixunPlayer-laun/app/src/main/assets/imgs/W35.png
new file mode 100644
index 0000000..cfa67ec
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W35.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W36.png b/XixunPlayer-laun/app/src/main/assets/imgs/W36.png
new file mode 100644
index 0000000..2b326a8
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W36.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W4.png b/XixunPlayer-laun/app/src/main/assets/imgs/W4.png
new file mode 100644
index 0000000..17a05d8
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W4.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W44.png b/XixunPlayer-laun/app/src/main/assets/imgs/W44.png
new file mode 100644
index 0000000..d829389
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W44.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W45.png b/XixunPlayer-laun/app/src/main/assets/imgs/W45.png
new file mode 100644
index 0000000..9129ef8
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W45.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W46.png b/XixunPlayer-laun/app/src/main/assets/imgs/W46.png
new file mode 100644
index 0000000..9129ef8
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W46.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W5.png b/XixunPlayer-laun/app/src/main/assets/imgs/W5.png
new file mode 100644
index 0000000..5f7cbd7
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W5.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W6.png b/XixunPlayer-laun/app/src/main/assets/imgs/W6.png
new file mode 100644
index 0000000..2e1a63c
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W6.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W7.png b/XixunPlayer-laun/app/src/main/assets/imgs/W7.png
new file mode 100644
index 0000000..d37cc41
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W7.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W8.png b/XixunPlayer-laun/app/src/main/assets/imgs/W8.png
new file mode 100644
index 0000000..a3394db
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W8.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/W9.png b/XixunPlayer-laun/app/src/main/assets/imgs/W9.png
new file mode 100644
index 0000000..8e98a1c
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/W9.png differ
diff --git a/XixunPlayer-laun/app/src/main/assets/imgs/logo3.png b/XixunPlayer-laun/app/src/main/assets/imgs/logo3.png
new file mode 100644
index 0000000..3b4ea20
Binary files /dev/null and b/XixunPlayer-laun/app/src/main/assets/imgs/logo3.png differ
diff --git a/XixunPlayer-laun/app/src/main/java/com/xixun/command/reply/ReplyBase.java b/XixunPlayer-laun/app/src/main/java/com/xixun/command/reply/ReplyBase.java
new file mode 100644
index 0000000..d2f446a
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/java/com/xixun/command/reply/ReplyBase.java
@@ -0,0 +1,10 @@
+package com.xixun.command.reply;
+
+import java.io.Serializable;
+
+
+public abstract class ReplyBase implements Serializable{
+
+ private static final long serialVersionUID = -3630726876519388513L;
+ public String commandId;
+}
diff --git a/XixunPlayer-laun/app/src/main/java/com/xixun/command/reply/TaskProgressReply.java b/XixunPlayer-laun/app/src/main/java/com/xixun/command/reply/TaskProgressReply.java
new file mode 100644
index 0000000..54b2a64
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/java/com/xixun/command/reply/TaskProgressReply.java
@@ -0,0 +1,18 @@
+package com.xixun.command.reply;
+
+public class TaskProgressReply extends ReplyBase {
+
+ private static final long serialVersionUID = 6264049742389542806L;
+ public int percent;
+ public String taskItemId;
+ public int speed;
+ public int remainingSeconds;
+
+ public TaskProgressReply(String commandId, String taskItemId, int percent, int sp, int rs) {
+ this.commandId = commandId;
+ this.taskItemId = taskItemId;
+ this.percent = percent;
+ this.speed = sp;
+ this.remainingSeconds = rs;
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/AIDLService.java b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/AIDLService.java
new file mode 100644
index 0000000..60ba352
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/AIDLService.java
@@ -0,0 +1,391 @@
+package com.xixun.xixunplayer;
+
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.xixun.command.reply.TaskProgressReply;
+import com.xixun.util.PlayerInfo;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashSet;
+
+import gnph.util.IOs;
+import gnph.util.JSList;
+import gnph.util.JSMap;
+import gnph.util.URLConn;
+
+public class AIDLService extends Service {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+ PlayerInfo.Stub binder = new PlayerInfo.Stub() {
+ @Override
+ public String getProgramName() throws RemoteException {
+ try {
+ String name = null;
+ var ins = MainActivity.ins;
+ if(ins!=null && ! ins.avas.isEmpty()) name = ins.avas.get(ins.curAva).name;
+ Util.println("Server getProgramName. <-"+name);
+ return name;
+ } catch (Exception e) {
+ Util.printStackTrace(e);
+ return null;
+ }
+ }
+
+ @Override
+ public String getVersion() throws RemoteException {
+ Util.println("Server getVersion. <-"+null);
+ return null;
+ }
+
+ @Override
+ public void setScreenWidth(int width) throws RemoteException {
+ Util.println("Server setScreenWidth. ->"+width);
+ }
+
+ @Override
+ public void setScreenHeight(int height) throws RemoteException {
+ Util.println("Server setScreenHeight. ->"+height);
+
+ }
+
+ @Override
+ public void taskScreenshot(String cmdId) throws RemoteException {
+ Util.println("Server taskScreenshot. ->"+cmdId);
+ }
+
+ @Override
+ public void setExternalTemperature(float t) throws RemoteException {
+
+ }
+
+ @Override
+ public void setInternalTemperature(float t) throws RemoteException {
+
+ }
+
+ @Override
+ public void setHumidity(float h) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean forcePlayProgram(String pid) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean finishForcePlay() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public String getCurProgramId() throws RemoteException {
+ Util.println("Server getCurProgramId ...");
+ return null;
+ }
+
+ @Override
+ public void setUSBProgramPwd(String pwd) throws RemoteException {
+
+ }
+
+ @SuppressLint("ResourceType")
+ @Override
+ public String executeJosnCommand(String jsonstr) throws RemoteException {
+ Util.println("Server executeJsonCommand ..."+jsonstr);//{"_type":"DeleteTask","id":"652522a0e81d1e000009201a","sendTo":"yzd-player"}
+ String commandId = null;
+ try {
+ var jsonBytes = jsonstr.getBytes(StandardCharsets.UTF_8);
+ var json = JSMap.from(jsonBytes);
+ var _type = json.stnn("_type");
+ commandId = json.stnn("id");
+ if(_type.equals("PlayXixunTask") || _type.equals("PlayProgramTask")) {
+ var preDownloadURL = json.str("preDownloadURL");
+ if(preDownloadURL==null) preDownloadURL = Util.serverURL+"file/download?id=";
+ var task = json.jsmap("task");
+ JSList jpages = task.jslist("items");
+ int proSize = 0;
+ var needDowns = new ArrayList();
+ var hases = new HashSet();
+ for(var pageMap : jpages) {
+ var _program = pageMap.jsmap("_program");
+ if(_program==null) continue;
+ proSize += _program.intg("totalSize");
+ var needDown = new NeedDowns();
+ needDown.prog = pageMap.stnn("_id");
+ needDowns.add(needDown);
+ JSList layers = _program.jslist("layers");
+ if(layers==null || layers.isEmpty()) continue;
+ for(int ll=layers.size()-1; ll>=0; ll--) {
+ var layer = new Prog.Layer();
+ JSList sources = layers.get(ll).jslist("sources");
+// var border = layers.get(ll).jsmap("border");
+// if(border!=null) {
+// }
+ for(var source : sources) {
+ var type = source.stnn("_type");
+ if(type.equals("Video") || type.equals("Audio") || type.equals("Image") || type.startsWith("DigitalClock") || type.equals("Timer") || type.startsWith("Environ")) {
+ var filename = source.str("id");
+ if(filename==null) continue;
+ var url = source.stnn("url");
+ if(! url.startsWith("http")) url = preDownloadURL + filename;
+ var file = new File(Util.programDir+"/"+filename);
+ if(file.exists() && file.length() > 0) {
+ proSize -= file.length();
+ hases.add(filename);
+ } else needDown.srcs.add(new Src(filename, url));
+ } else if(type.startsWith("MultiPng") || type.equals("SplitText")) {
+ JSList imgs = source.jslist("arrayPics");
+ if(imgs.isEmpty()) continue;
+ for(var img : imgs) {
+ var filename = img.str("id");
+ if(filename==null) continue;
+ var url = img.stnn("url");
+ if(! url.startsWith("http")) url = preDownloadURL + filename;
+ var file = new File(Util.programDir+"/"+filename);
+ if(file.exists() && file.length() > 0) {
+ proSize -= file.length();
+ hases.add(filename);
+ } else needDown.srcs.add(new Src(filename, url));
+ }
+ }
+ }
+ }
+ }
+ int finalProSize = proSize;
+ String finalCommandId = commandId;
+ var notificationURL = json.str("notificationURL");
+ new Thread(()->{
+ Util.deleteFiles(finalProSize, hases, "");
+ for(var needDown : needDowns) {
+ int cnt = 0;
+ for(var src : needDown.srcs) {
+ try {
+ var in = new URLConn(src.url).in();
+ var fout = new FileOutputStream(Util.programDir+"/"+src.filename);
+ IOs.writeCloseIn(fout, in);
+ fout.flush();
+ fout.getFD().sync();
+ fout.close();
+ } catch (Exception e) {
+ Util.printStackTrace(e);
+ }
+ cnt++;
+ var progress = cnt*100/needDown.srcs.size();
+ if(cnt != needDown.srcs.size()) {
+ if(notificationURL==null) {
+ var intent = new Intent("xixun.intent.action.REPLY");
+ intent.putExtra("reply", new TaskProgressReply(finalCommandId, needDown.prog, progress, 500, 3));
+ sendBroadcast(intent);
+ } else {
+ try {
+ new URLConn(notificationURL).timeout(5000).writeJson(new JSMap(
+ "commandId", finalCommandId,
+ "taskItemId", needDown.prog,
+ "progress", progress).toStr()).read();
+ } catch (Exception e) {
+ Util.printStackTrace(e);
+ }
+ }
+ }
+ }
+ if(notificationURL==null) {
+ var intent = new Intent("xixun.intent.action.REPLY");
+ intent.putExtra("reply", new TaskProgressReply(finalCommandId, needDown.prog, 100, 400, 0));
+ sendBroadcast(intent);
+ } else {
+ try {
+ new URLConn(notificationURL).timeout(5000).writeJson(new JSMap(
+ "commandId", finalCommandId,
+ "taskItemId", needDown.prog,
+ "progress", 100).toStr()).read();
+ } catch (Exception e) {
+ Util.printStackTrace(e);
+ }
+ }
+ }
+ var acti = MainActivity.ins;
+ if(acti!=null) acti.runOnUiThread(() -> {
+ Util.curProgDir = Util.programDir;
+ acti.initProg(jsonBytes);
+ });
+ else {
+ Util.curProgDir = Util.programDir;
+ try {
+ var fOut = new FileOutputStream(Util.curProgDir + "/program");
+ fOut.write(jsonBytes);
+ fOut.flush();
+ fOut.getFD().sync();
+ fOut.close();
+ } catch (Exception e) {
+ Util.printStackTrace(e);
+ }
+ var intent = new Intent(AIDLService.this, MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ AIDLService.this.startActivity(intent);
+ }
+ var intent = new Intent("com.xixun.AccessibilityService");
+ intent.putExtra("newProgram", "platform");
+ sendBroadcast(intent);
+ }).start();
+ return new JSMap(
+ "_type", "Success",
+ "result", "received command",
+ "cardId", Util.getCardId(),
+ "commandId", commandId
+ ).toString();
+ } else if(_type.equals("DeleteTask")) {
+ MainActivity.ins.runOnUiThread(() -> MainActivity.ins.delProgFile(Util.programDir));
+ return new JSMap(
+ "_type", "DataCallback",
+ "result", "received command",
+ "cardId", Util.getCardId(),
+ "commandId", commandId
+ ).toString();
+ } else if(_type.equals("PlayerStateCommand")) {
+ var descs = Util.getState(MainActivity.ins.state);
+ return new JSMap(
+ "_type", "Success",
+ "cardId", Util.getCardId(),
+ "commandId", commandId,
+ "code", MainActivity.ins.state,
+ "des_en", descs[0],
+ "des", descs[1]
+ ).toString();
+ } else if(_type.equals("SetPlayerBackground")) {
+ var url = json.str("url");
+ if(url==null) new File(Util.backImgFile).delete();
+ else {
+ var fout = new FileOutputStream(Util.backImgFile);
+ IOs.write(fout, new URLConn(url).in());
+ fout.flush();
+ fout.getFD().sync();
+ fout.close();
+ }
+ MainActivity.ins.runOnUiThread(() -> {
+ MainActivity.ins.backView.cosImg = url==null ? null : BitmapFactory.decodeFile(Util.backImgFile, Util.noScaled);
+ MainActivity.ins.backView.invalidate();
+ });
+ return new JSMap(
+ "_type", "Success",
+ "cardId", Util.getCardId(),
+ "commandId", commandId
+ ).toString();
+ } else if(_type.equals("GetPlayerBackground")) {
+ var backImg = new File(Util.backImgFile);
+ var img = Base64.getEncoder().encodeToString(IOs.readBytesClose(backImg.exists() ? new FileInputStream(backImg) : MainActivity.ins.getResources().openRawResource(R.drawable.back)));
+ return new JSMap(
+ "_type", "DataCallback",
+ "cardId", Util.getCardId(),
+ "commandId", commandId,
+ "img", img
+ ).toString();
+ } else if(_type.equalsIgnoreCase("getProgramTask")) {
+ var task = JSMap.fromClose(new FileInputStream(Util.programDir+"/program")).jsmap("task");
+ return new JSMap(
+ "_type", "ProgramTaskCallback",
+ "cardId", Util.getCardId(),
+ "commandId", commandId,
+ "task", task
+ ).toString();
+ }
+ return new JSMap(
+ "_type", "Error",
+ "errorMessage", "Unknown Type: "+_type,
+ "cardId", Util.getCardId(),
+ "commandId", commandId
+ ).toString();
+ } catch (Exception e) {
+ Util.printStackTrace(e);
+ return new JSMap(
+ "_type", "Error",
+ "errorMessage", Util.toStr(e),
+ "cardId", Util.getCardId(),
+ "commandId", commandId
+ ).toString();
+ }
+ }
+
+ @Override
+ public void pausePlayer(boolean b) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isPause() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean clearTasks() throws RemoteException {
+ MainActivity.ins.runOnUiThread(() -> MainActivity.ins.delProgFile(Util.programDir));
+ return true;
+ }
+
+ @Override
+ public int countOfPrograms(int type) throws RemoteException {
+ var cnt = MainActivity.ins!=null && MainActivity.ins.progView!=null ? MainActivity.ins.progView.pages.size() : 0;
+ Util.println("Server countOfPrograms. <-"+cnt);
+ return cnt;
+ }
+
+ @Override
+ public void playInsertTask(String pid) throws RemoteException {
+
+ }
+
+ @Override
+ public void stopInsertTask(String pid) throws RemoteException {
+
+ }
+
+ @Override
+ public String getProgramTask() throws RemoteException {
+ try {
+ return IOs.readStrClose(new FileInputStream(Util.programDir+"/program"));
+ } catch (Exception e) {
+ Util.printStackTrace(e);
+ return Util.toStr(e);
+ }
+ }
+
+ @Override
+ public void setUploadLogUrl(String playLog) throws RemoteException {
+
+ }
+
+ @Override
+ public String getUploadLogUrl() throws RemoteException {
+ return null;
+ }
+ };
+
+ static class Src {
+ String filename;
+ String url;
+
+ public Src(String filename, String url) {
+ this.filename = filename;
+ this.url = url;
+ }
+ }
+ static class NeedDowns {
+ String prog;
+ ArrayList srcs = new ArrayList<>();
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/AbsLayout.java b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/AbsLayout.java
new file mode 100644
index 0000000..b00610b
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/AbsLayout.java
@@ -0,0 +1,107 @@
+package com.xixun.xixunplayer;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class AbsLayout extends ViewGroup {
+
+ public AbsLayout(Context context) {
+ this(context, null);
+ }
+
+ public AbsLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AbsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AbsLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int count = getChildCount();
+ int maxHeight = 0;
+ int maxWidth = 0;
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ for(int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if(child.getVisibility() != GONE) {
+ int childRight;
+ int childBottom;
+ var lp = (LayoutParams) child.getLayoutParams();
+ childRight = lp.x + child.getMeasuredWidth();
+ childBottom = lp.y + child.getMeasuredHeight();
+ maxWidth = Math.max(maxWidth, childRight);
+ maxHeight = Math.max(maxHeight, childBottom);
+ }
+ }
+ maxWidth += getPaddingLeft() + getPaddingRight();
+ maxHeight += getPaddingTop() + getPaddingBottom();
+ maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+ maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+ setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0), resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(0, 0, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if(child.getVisibility() != GONE) {
+ var lp = (LayoutParams) child.getLayoutParams();
+ int childLeft = getPaddingLeft() + lp.x;
+ int childTop = getPaddingTop() + lp.y;
+ child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight());
+ }
+ }
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams;
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new LayoutParams(p);
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ public static class LayoutParams extends ViewGroup.LayoutParams {
+
+ public int x;
+ public int y;
+
+ public LayoutParams(int x, int y, int width, int height) {
+ super(width, height);
+ this.x = x;
+ this.y = y;
+ }
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+ }
+}
diff --git a/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/BackView.java b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/BackView.java
new file mode 100644
index 0000000..e7b42a2
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/BackView.java
@@ -0,0 +1,31 @@
+package com.xixun.xixunplayer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.view.View;
+
+public class BackView extends View {
+
+ private Bitmap img;
+ Bitmap cosImg;
+ Rect rect = new Rect();
+
+ public BackView(Context context) {
+ super(context);
+ img = BitmapFactory.decodeResource(context.getResources(), R.drawable.back, Util.noScaled);
+ cosImg = BitmapFactory.decodeFile(Util.backImgFile, Util.noScaled);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ rect.right = Util.screenWidth;
+ rect.bottom = Util.screenHeight;
+ canvas.clipRect(rect);
+ if(cosImg!=null) canvas.drawBitmap(cosImg, null, rect, null);
+ else if(img!=null) for(int y=0; y> "+intent.getAction());
+ Util.initDir(context);
+ if(MainService.ins==null) context.startService(new Intent(context, MainService.class));
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/IntentReceiver.java b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/IntentReceiver.java
new file mode 100644
index 0000000..d8401df
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/IntentReceiver.java
@@ -0,0 +1,7 @@
+package com.xixun.xixunplayer;
+
+import android.content.Intent;
+
+public interface IntentReceiver {
+ void onReceive(Intent intent);
+}
diff --git a/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/MainActivity.java b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/MainActivity.java
new file mode 100644
index 0000000..d470880
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/MainActivity.java
@@ -0,0 +1,558 @@
+package com.xixun.xixunplayer;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+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.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.StrictMode;
+import android.view.Choreographer;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.xixun.joey.aidlset.CardService;
+import com.xixun.xy.conn.aidl.ConnService;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import gnph.util.Chsets;
+import gnph.util.JSList;
+import gnph.util.JSMap;
+
+public class MainActivity extends Activity implements Choreographer.FrameCallback {
+
+ public static MainActivity ins;
+ ArrayList reces = new ArrayList<>();
+ ArrayList services = new ArrayList<>();
+ public Intent environIntent = new Intent();
+ HashSet environs = new HashSet<>();
+ BackView backView;
+ Prog progView, insView;
+ long launchMilli = System.currentTimeMillis();
+ long syncMs;
+ int state;
+
+ @RequiresApi(api = Build.VERSION_CODES.R)
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ins = this;
+ Util.println("==>> MainActivity onCreate >>>> this "+hashCode()+" Thread: "+Thread.currentThread().getId());
+
+ StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());
+ setContentView(backView = new BackView(MainActivity.this));
+ state = 5;
+
+ if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) init();
+ else {
+ Util.println("---- No permission, Try again ...");
+ Util.makeText(this, "No permission, Try again ...").show();
+ 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(backView!=null) return;
+ if(requestCode==999 && grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED) init();
+ else {
+ Util.println("---- Request Permission Failed");
+ Util.makeText(this, "Request Permission Failed").show();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Util.println("==<< MainActivity onDestroy <<<< this "+hashCode());
+ state = 8;
+ if(insView!=null) {
+ insView.release();
+ insView = null;
+ }
+ if(progView!=null) {
+ progView.release();
+ progView = null;
+ }
+ ins = null;
+ for(var rece : reces) unregisterReceiver(rece);
+ for(var service : services) unbindService(service);
+ System.gc();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Util.println(" ==>> MainActivity onStart >> "+hashCode());
+ }
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Util.println(" ==<< MainActivity onStop << "+hashCode());
+ }
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ Util.println(" ==>> MainActivity onRestart >> "+hashCode());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Util.println(" ==>> MainActivity onResume >> "+hashCode());
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ Util.println(" ==<< MainActivity onPause << "+hashCode());
+ }
+
+ CardService serviCard;
+ ConnService serviXy;
+ Intent intenCard;
+ ServiceConnection connCard;
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ public void init() {
+ Util.initDir(this);
+ if(MainService.ins==null) startService(new Intent(this, MainService.class));
+
+ connCard = new ServiceConnection() {
+ public void onServiceDisconnected(ComponentName name) {
+ serviCard = null;
+ Util.println("<-<- AIDL Service cardsystem Disconnected");
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder iBinder) {
+ unbindService(this);
+ services.remove(this);
+ Util.println("->-> AIDL Service cardsystem Connected");
+ serviCard = CardService.Stub.asInterface(iBinder);
+ try {
+ Util.isScreenOn = serviCard.isScreenOpen();
+ Util.screenWidth = serviCard.getScreenWidth();
+ Util.screenHeight = serviCard.getScreenHeight();
+ Util.println(" IsScreenOn: "+Util.isScreenOn+" screen: "+Util.screenWidth+" x "+Util.screenHeight);
+ backView.invalidate();
+ if(Util.isScreenOn) initProg();
+ else state = 8;
+ } catch (Exception e) {
+ Util.makeText(MainActivity.this, Util.toStr(e)).show();
+ Util.printStackTrace(e);
+ }
+ }
+ };
+ intenCard = new Intent("com.xixun.joey.aidlset.SettingsService");
+ intenCard.setPackage("com.xixun.joey.cardsystem");
+ bindService(intenCard, connCard, Context.BIND_AUTO_CREATE);
+ services.add(connCard);
+
+ var connXy = new ServiceConnection() {
+ public void onServiceDisconnected(ComponentName name) {
+ serviXy = null;
+ Util.println("<-<- AIDL Service xy.conn Disconnected");
+ }
+ public void onServiceConnected(ComponentName name, IBinder iBinder) {
+ unbindService(this);
+ services.remove(this);
+ Util.println("->-> AIDL Service xy.conn Connected");
+ serviXy = ConnService.Stub.asInterface(iBinder);
+ try {
+ Util.serverURL = serviXy.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 += "/";
+ }
+ } catch (Exception e) {
+ Util.makeText(MainActivity.this, Util.toStr(e)).show();
+ Util.printStackTrace(e);
+ }
+ }
+ };
+ var intenXy = new Intent("xixun.intent.action.CONNECTION_INFO");
+ intenXy.setPackage("com.xixun.xy.conn");
+ bindService(intenXy, connXy, Context.BIND_AUTO_CREATE);
+ services.add(connXy);
+
+ reces.clear();
+ BroadcastReceiver rece;
+ registerReceiver(rece = 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(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();
+ }
+ }, new IntentFilter("com.xixun.action.PAUSE_PLAYER"));
+ reces.add(rece);
+
+ registerReceiver(rece = new BroadcastReceiver(){
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Util.println("Receive CHANGE_COMPANYID");
+ try {
+ if(serviXy!=null && serviXy.asBinder().isBinderAlive()) {
+ Util.serverURL = serviXy.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 += "/";
+ }
+ } else {
+ Util.println(" bindService");
+ bindService(intenXy, connXy, Context.BIND_AUTO_CREATE);
+ }
+ } catch (Exception e) {
+ Util.makeText(MainActivity.this, Util.toStr(e)).show();
+ Util.printStackTrace(e);
+ }
+ }
+ }, new IntentFilter("com.xixun.joey.CHANGE_COMPANYID"));
+ reces.add(rece);
+
+ registerReceiver(rece = 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"));
+ reces.add(rece);
+
+ registerReceiver(rece = 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 || ! progView.isShown() || code > progView.pages.size()) return;
+ var ms = System.currentTimeMillis();
+ if(! avas.isEmpty()) {
+ avas.get(curAva).hide();
+ if(code > 0 && avas.get(curAva)==progView.pages.get(code-1)) for(var call : progView.calls) call.doFrame(ms);
+ }
+ if(code > 0) {
+ avas.clear();
+ var page = progView.pages.get(code-1);
+ avas.add(page);
+ curAva = 0;
+ curTimes = 1;
+ waitTo = 0; //点播
+ page.setMillis(ms, ms);
+ if(state != 6) {
+ setContentView(progView);
+ state = 6;
+ }
+ } else {
+ waitTo = Long.MAX_VALUE;
+ syncProg(ms, 0);
+ }
+ } catch (Throwable e) {
+ Util.makeText(MainActivity.this, Util.toStr(e)).show();
+ Util.printStackTrace(e);
+ }
+ }
+ }, new IntentFilter("com.xixun.yzd.REMOTE_CONTROL"));
+ reces.add(rece);
+ }
+
+ public void stopProg() {
+ avas.clear();
+ curAva = 0;
+ curTimes = 1;
+ if(insView!=null) {
+ insView.release();
+ insView = null;
+ }
+ if(progView!=null) {
+ progView.release();
+ progView = null;
+ }
+ setContentView(backView);
+ System.gc();
+ }
+ public boolean delProgFile(String dir) {
+ stopProg();
+ state = 4;
+ return Util.delProgFile(dir);
+ }
+ public void initProg() {
+ state = 1;
+ try {
+ Util.println("\nParse Insert Prog Json");
+ var root = JSMap.fromClose(new BufferedInputStream(new FileInputStream(Util.curProgDir + "/insert")));
+ var task = root.jsmap("task");
+ if(task==null && root.containsKey("layers")) task = new JSMap("items", new JSList<>(new JSMap("_program", root)));
+ if(task!=null) {
+ var view = new Prog(task, this);
+ if(view.getChildCount()!=0) setContentView(insView = view);
+ }
+ } catch(FileNotFoundException ignored) {
+ } catch(Throwable e) {
+ state = 7;
+ Util.printStackTrace(e);
+ }
+ try {
+ Util.println("\nParse Prog Json");
+ var root = JSMap.fromClose(new BufferedInputStream(new FileInputStream(Util.curProgDir + "/program")));
+ var task = root.jsmap("task");
+ if(task==null && root.containsKey("layers")) task = new JSMap("items", new JSList<>(new JSMap("_program", root)));
+ if(task==null) Util.println(root.isEmpty() ? " Empty program JSON\n" : " Error: task==null\n");
+ else {
+ var view = new Prog(task, this);
+ if(view.getChildCount()==0) Util.println(" Error: ChildCount==0\n");
+ else setContentView(progView = view);
+ }
+ } catch(FileNotFoundException e) {
+ Util.println(""+e);
+ } catch(Throwable e) {
+ state = 7;
+ Util.makeText(this, Util.toStr(e)).show();
+ Util.printStackTrace(e);
+ }
+ if(insView!=null || progView!=null) {
+ state = 5;
+ Util.println("Init Sync");
+ syncProg((System.currentTimeMillis()+999)/1000*1000, 0);
+ if(canAdd) {
+ choreographer.postFrameCallback(this);
+ canAdd = false;
+ }
+ } else if(state != 7) state = 3;
+ }
+ 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("_program", root)));
+ }
+ var view = new Prog(task, this);
+ if(view.getChildCount()==0) {
+ if(! view.isInsert) state = 7;
+ Util.println(" Error: ChildCount==0\n");
+ Util.println(new String(json, Chsets.UTF8));
+ return;
+ }
+ if(view.isInsert) {
+ if(insView!=null) insView.release();
+ insView = view;
+ setContentView(insView);
+ } else {
+ if(progView!=null) progView.release();
+ progView = view;
+ setContentView(progView);
+ }
+ var fOut = new FileOutputStream(Util.curProgDir + (view.isInsert?"/insert":"/program"));
+ fOut.write(json);
+ fOut.flush();
+ fOut.getFD().sync();
+ fOut.close();
+ state = 5;
+ System.gc();
+ Util.println("Init Sync");
+ syncProg((System.currentTimeMillis()+999)/1000*1000, 0);
+ 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));
+ }
+ }
+
+ ArrayList avas = new ArrayList<>();
+ int curAva, curTimes = 1;
+ long waitTo = Long.MAX_VALUE;
+ boolean isInsert;
+
+ Choreographer choreographer = Choreographer.getInstance();
+ boolean canAdd = true;
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ if(progView == null && insView==null) {
+ canAdd = true;
+ return;
+ }
+ choreographer.postFrameCallback(this);
+ canAdd = false;
+ var milli = System.currentTimeMillis();
+ if(avas.isEmpty()) {
+ if(milli >= waitTo) {
+ waitTo = Long.MAX_VALUE;
+ Util.println("wait sync");
+ syncProg(milli, 0);
+ if(insView!=null) for(var call : insView.calls) call.doFrame(milli);
+ if(progView!=null) for(var call : progView.calls) call.doFrame(milli);
+ }
+ return;
+ }
+ var lastPage = avas.get(curAva);
+ if(milli < lastPage.endMilli) lastPage.showHideSrcs(milli);
+ else {
+ lastPage.hide();
+ if(isInsert) {
+ if(--lastPage.repeatTimes<=0 && ++curAva >= avas.size()) {
+ var isDiff = milli-lastPage.endMilli>=1000;
+ syncProg(isDiff ? milli : lastPage.endMilli, milli);
+ if(insView!=null) for(var call : insView.calls) call.doFrame(milli);
+ if(progView!=null) for(var call : progView.calls) call.doFrame(milli);
+ return;
+ }
+ } else {
+ if(waitTo > 0) { //waitTo==0 为点播,不换页
+ if(curTimes < lastPage.repeatTimes) curTimes++;
+ else {
+ curTimes = 1;
+ if(++curAva >= avas.size()) {
+ var isDiff = milli-lastPage.endMilli>=1000;
+ syncProg(isDiff ? milli : lastPage.endMilli, milli);
+ if(insView!=null) for(var call : insView.calls) call.doFrame(milli);
+ if(progView!=null) for(var call : progView.calls) call.doFrame(milli);
+ return;
+ }
+ }
+ } else if(milli-lastPage.endMilli >= 5000) {
+ Util.println("System Time Changed: "+Util.dateFmt.format(lastPage.endMilli)+" -> "+Util.dateFmt.format(milli));
+ lastPage.endMilli = milli;
+ }
+ }
+ avas.get(curAva).setMillis(lastPage.endMilli, milli);
+ Util.println("curAva: "+curAva+" endMs: "+avas.get(curAva).endMilli);
+ }
+ if(isInsert) for(var call : insView.calls) call.doFrame(milli);
+ else for(var call : progView.calls) call.doFrame(milli);
+ }
+
+ void syncProg(long milli, long cur) {
+ if(cur==0) cur = milli;
+ Util.println("\nSyncProg");
+ avas.clear();
+ curAva = 0;
+ curTimes = 1;
+ isInsert = false;
+ if(insView!=null) {
+ for(int i=0; i reces = new ArrayList<>();
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ ins = this;
+ Util.println("==>> MainService onCreate >>>> "+hashCode()+" Thread: "+Thread.currentThread().getId());
+ TCPThread.startServer(3333);
+// if(MainActivity.ins!=null) return;
+// var intent = new Intent(this, MainActivity.class);
+// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+// startActivity(intent);
+
+ BroadcastReceiver rece;
+ var intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
+ intentFilter.addDataScheme("file");
+ registerReceiver(rece = new BroadcastReceiver(){
+ long lastMs;
+ final char[] pass = {'8','8','8'};
+ @Override
+ public void onReceive(Context context, Intent intent) { // in UI Thread
+ var path = intent.getData().getPath();
+ Util.println("\nMEDIA_MOUNTED path: "+path);
+ var ms = System.currentTimeMillis();
+ if(ms-lastMs<1000) return;
+ lastMs = ms;
+ var acti = MainActivity.ins;
+ Util.makeText(MainService.this, "MEDIA_MOUNTED path: "+path+"\nImporting 正在导入 ...").show();
+ new Thread(()->{
+ try {
+ var zip = new ZipFile(path+"/program.zip");
+ if(zip.isEncrypted()) zip.setPassword(pass);
+ long size = 0;
+ ByteArrayOutputStream jsonOut = null;
+ var headers = zip.getFileHeaders();
+ for(var header : headers) {
+ size += header.getUncompressedSize();
+ if("program".equals(header.getFileName())) IOs.writeClose(jsonOut = new ByteArrayOutputStream(), zip.getInputStream(header));
+ }
+ if(jsonOut==null) {
+ Util.println("No program File");
+ if(acti!=null) acti.runOnUiThread(() -> Util.makeText(acti, "No program File").show());
+ return;
+ }
+ if(size==0) {
+ Util.println("zip size is 0");
+ if(acti!=null) acti.runOnUiThread(() -> Util.makeText(acti, "zip size is 0").show());
+ return;
+ }
+ String suffix;
+ if(Util.isLaun) {
+ suffix = "USB";
+ new File(Util.programDir + suffix).mkdirs();
+ } else suffix = "";
+ Util.deleteFiles(size, null, suffix);
+ for(var header : headers) if(! "program".equals(header.getFileName())) {
+ Util.println(" name: " + header.getFileName());
+ var fOut = new FileOutputStream(Util.programDir + suffix + "/" + header.getFileName());
+ IOs.writeCloseIn(fOut, zip.getInputStream(header));
+ fOut.flush();
+ fOut.getFD().sync();
+ fOut.close();
+ }
+ var json = jsonOut.toByteArray();
+ Util.println("Import Succeed");
+ if(acti!=null) acti.runOnUiThread(() -> {
+ Util.makeText(acti, "Import Succeed 导入成功").show();
+ Util.curProgDir = Util.programDir + suffix;
+ acti.initProg(json);
+ });
+ else {
+ Util.curProgDir = Util.programDir + suffix;
+ var fOut = new FileOutputStream(Util.curProgDir + "/program");
+ fOut.write(json);
+ fOut.flush();
+ fOut.getFD().sync();
+ fOut.close();
+ var inten = new Intent(MainService.this, MainActivity.class);
+ inten.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(inten);
+ }
+ var inten = new Intent("com.xixun.AccessibilityService");
+ inten.putExtra("newProgram", "USB");
+ sendBroadcast(inten);
+ } catch (Exception e) {
+ Util.printStackTrace(e);
+ if(acti!=null) acti.runOnUiThread(() -> Util.makeText(acti, Util.toStr(e)).show());
+ }
+ }).start();
+ }
+ }, intentFilter);
+ reces.add(rece);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Util.println("==<< MainService onDestroy <<<< this "+hashCode());
+ ins = null;
+ for(var rece : reces) {
+ try {
+ unregisterReceiver(rece);
+ } catch (Throwable ignored){
+ }
+ }
+ System.gc();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/Prog.java b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/Prog.java
new file mode 100644
index 0000000..041896a
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/Prog.java
@@ -0,0 +1,790 @@
+package com.xixun.xixunplayer;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.net.Uri;
+import android.os.CountDownTimer;
+import android.speech.tts.TextToSpeech;
+import android.view.Choreographer;
+import android.view.View;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebView;
+import android.widget.ImageView;
+
+import java.io.File;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+
+import gnph.util.JSList;
+import gnph.util.JSMap;
+import pl.droidsonroids.gif.GifImageView;
+
+@SuppressLint("ViewConstructor")
+public class Prog extends AbsLayout {
+
+ ArrayList pages = new ArrayList<>();
+ ArrayList calls = new ArrayList<>();
+ boolean isInsert;
+
+ @SuppressLint("SetJavaScriptEnabled")
+ public Prog(JSMap task, Context context) {
+ super(context);
+ isInsert = task.bool("insert");
+ JSList jpages = task.jslist("items");
+ JSList partLengths = task.jslist("partLengths");
+ var isVertical = task.bool("isVertical");
+ var partObj = task;
+ if(partLengths==null) {
+ try {
+ var _program = jpages.get(0).jsmap("_program");
+ partLengths = _program.jslist("splitWidths");
+ isVertical = _program.bool("isVer");
+ partObj = _program;
+ } catch (Throwable ignored){}
+ }
+ var width = partObj.intg("width");
+ var height = partObj.intg("height");
+ AbsLayout box;
+ if(partLengths==null || partLengths.size() <= 1) box = this;
+ else {
+ box = new AbsLayout(context);
+ addView(box, new AbsLayout.LayoutParams(0, 0, width, height));
+ var mask = new View(context);
+ mask.setBackgroundColor(0xff000000);
+ var len0 = partLengths.get(0).intValue();
+ addView(mask, isVertical ? new AbsLayout.LayoutParams(0, len0, width, height - len0) : new AbsLayout.LayoutParams(len0, 0, width - len0, height));
+ int x = 0, y = 0;
+ for(int i=1; i layers = _program.jslist("layers");
+ var isSimple = _program.intg("version")==2;
+ if(layers==null || layers.isEmpty()) continue;
+ if(isSimple) {
+ width = _program.intg("width", Util.screenWidth);
+ height = _program.intg("height", Util.screenHeight);
+ }
+ var page = new Page();
+ page.name = _program.str("name");
+ page.repeatTimes = pageMap.intg("repeatTimes", 1);
+ page.parse(pageMap.jslist("schedules"));
+ var waitAudio = pageMap.bool("waitAudio");
+ HashMap videoMap = new HashMap<>();
+ for(int ll=layers.size()-1; ll>=0; ll--) {
+ var layer = new Layer();
+ layer.isLoop = layers.get(ll).bool("repeat");
+ JSList sources = layers.get(ll).jslist("sources");
+ var border = layers.get(ll).jsmap("border");
+ SrcBorder bdEle = null;
+ int bdWidth = 0, bdStart = Integer.MAX_VALUE, bdEnd = 0;
+ if(border!=null) {
+ bdEle = new SrcBorder(this, Util.curProgDir+"/"+border.stnn("img"), border.stnn("eff"), border.intg("speed"));
+ bdWidth = bdEle.img.getHeight();
+ }
+ var src = new Source();
+ for(var source : sources) {
+ src.type = source.stnn("_type");
+ if(src.type.isEmpty()) continue;
+ var dur = source.intg("timeSpan")*1000;
+ if(dur==0) {
+ Util.println("\nError: timeSpan is 0. _type: "+src.type);
+ continue;
+ }
+ var geo = isSimple ? new AbsLayout.LayoutParams(0, 0, width, height) : new AbsLayout.LayoutParams(source.intg("left")+bdWidth, source.intg("top")+bdWidth, source.intg("width")-bdWidth-bdWidth, source.intg("height")-bdWidth-bdWidth);
+ var notAudio = ! src.type.equals("Audio");
+ if(notAudio) {
+ if(geo.width<=0 || geo.height<=0) {
+ Util.println("\nError: width or height is 0. _type: "+src.type+" width: "+geo.width+" height: "+geo.height);
+ continue;
+ }
+ if(box != this && ((geo.y>=height && height>0) || (geo.x>=width && width>0))) {
+ Util.println("\nError: y>=height or x>=width. _type: "+src.type+" width: "+width+" height: "+height+" x: "+geo.x+" y: "+geo.y);
+ continue;
+ }
+ }
+ src.startTime = isSimple ? (layer.srcs.isEmpty() ? 0 : layer.srcs.get(layer.srcs.size()-1).endTime) : source.intg("playTime")*1000;
+ if(bdStart > src.startTime) bdStart = src.startTime;
+ src.endTime = src.startTime + dur;
+ if(bdEnd < src.endTime) bdEnd = src.endTime;
+ if(layer.dur < src.endTime) layer.dur = src.endTime;
+ if(notAudio || waitAudio) {
+ if(page.sDur < src.endTime) page.sDur = src.endTime;
+ } else if(page.audioDur < src.endTime) page.audioDur = src.endTime;
+
+ src.entryDur = source.intg("entryEffectTimeSpan")*60;
+ if(src.entryDur > 0) {
+ var effect = source.str("entryEffect");
+ if(effect == null || effect.equalsIgnoreCase("None")) src.entryDur = 0;
+ else if(effect.equalsIgnoreCase("Random")) src.isEntryRand = true;
+ else {
+ effect = effect.replace("MOVING", "MOVE");
+ effect = effect.replace("LEFT_TOP", "TL");
+ effect = effect.replace("RIGHT_TOP", "TR");
+ effect = effect.replace("RIGHT_BOTTOM", "BR");
+ effect = effect.replace("LEFT_BOTTOM", "BL");
+ effect = effect.replace("ALPHA", "FADE");
+ effect = effect.replace("_IN", "");
+ effect = effect.replace("_OUT", "");
+ try {
+ src.entryEff = Effect.valueOf(effect);
+ } catch (Throwable e) {
+ if(effect.equalsIgnoreCase("ROTATE_RIGHT")) src.entryEff = Effect.ROTATE;
+ else if(effect.equalsIgnoreCase("ROTATE_LEFT")) src.entryEff = Effect.ROTATE_R;
+ else src.entryDur = 0;
+ }
+ }
+ }
+ src.exitDur = source.intg("exitEffectTimeSpan")*60;
+ if(src.exitDur > 0) {
+ var effect = source.str("exitEffect");
+ if(effect == null || effect.equalsIgnoreCase("None")) src.exitDur = 0;
+ else if(effect.equalsIgnoreCase("Random")) src.isExitRand = true;
+ else {
+ effect = effect.replace("MOVING", "MOVE");
+ effect = effect.replace("LEFT_TOP", "TL");
+ effect = effect.replace("RIGHT_TOP", "TR");
+ effect = effect.replace("RIGHT_BOTTOM", "BR");
+ effect = effect.replace("LEFT_BOTTOM", "BL");
+ effect = effect.replace("ALPHA", "FADE");
+ effect = effect.replace("_IN", "");
+ effect = effect.replace("_OUT", "");
+ try {
+ src.exitEff = Effect.valueOf(effect);
+ } catch (Throwable e) {
+ if(effect.equalsIgnoreCase("ROTATE_RIGHT")) src.exitEff = Effect.ROTATE;
+ else if(effect.equalsIgnoreCase("ROTATE_LEFT")) src.exitEff = Effect.ROTATE_R;
+ else src.exitDur = 0;
+ }
+ }
+ }
+ if(src.exitDur!=0) src.exitStart = dur*60/1000 - src.exitDur;
+
+ src.alpha = (float) source.dbl("opacity", 1);
+ var breathe = source.dbl("breathe");
+ if(breathe > 0) src.breathe = (int) Math.round(60 / breathe);
+ var blink = source.dbl("blink");
+ if(blink > 0) src.blinkHalf = (int) Math.round(30 / blink);
+ src.rotate = (float) source.dbl("rotate");
+ src.scaleX = (float) source.dbl("scaleX", 1);
+ src.scaleY = (float) source.dbl("scaleY", 1);
+
+ var id = source.str("id");
+ var fileExt = source.stnn("fileExt");
+ if(id!=null && fileExt.startsWith(".") && new File(Util.curProgDir + "/" + id + fileExt).exists()) id += fileExt;
+ src.view = null;
+ if(src.type.equals("Image")) {
+ if(id==null) continue;
+ var isGif = fileExt.toLowerCase().endsWith("gif");
+ if(isGif) {
+ var imgView = new GifImageView(context);
+ imgView.setImageURI(src.uri = Uri.fromFile(new File(Util.curProgDir + "/" + id)));
+ imgView.setScaleType(ImageView.ScaleType.FIT_XY);
+ src.view = imgView;
+ } else {
+ var imgView = new ImageView(context);
+ var file = Util.curProgDir+"/"+id;
+ new Thread(()->{
+ try {
+ var ttt = System.currentTimeMillis();
+ var img = BitmapFactory.decodeFile(file);
+// if(img.getByteCount()>1500*1500*4) {
+// var matrix = new Matrix();
+// matrix.setScale(0.5f, 0.5f);
+// img = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);
+// }
+ ttt = System.currentTimeMillis() - ttt;
+ Util.println(" img "+img.getWidth()+"x"+img.getHeight()+" "+ttt);
+ Bitmap finalImg = img;
+ MainActivity.ins.runOnUiThread(() -> imgView.setImageBitmap(finalImg));
+ } catch (Throwable e) {
+ var stackTrace = Util.toStackTrace(e);
+ Util.println(stackTrace);
+ var img = Bitmap.createBitmap(600, 400, Bitmap.Config.ARGB_8888);
+ var paint = new Paint();
+ paint.setColor(Color.RED);
+ new Canvas(img).drawText(stackTrace, 0, 0, paint);
+ MainActivity.ins.runOnUiThread(() -> imgView.setImageBitmap(img));
+ }
+ }).start();
+ imgView.setScaleType(ImageView.ScaleType.FIT_XY);
+ src.view = imgView;
+ }
+ } else if(src.type.endsWith("Video")) {
+ var isLive = src.type.startsWith("Live");
+ var url = source.str("url");
+ if(isLive) {
+ if(url==null) continue;
+ } else if(id==null) continue;
+ var key = isLive ? url : id + src.startTime + src.endTime;
+ var exist = videoMap.get(key);
+ if(exist!=null) {
+ var geoOld = (AbsLayout.LayoutParams) exist.getLayoutParams();
+ if(geo.width*geo.height > geoOld.width*geoOld.height) {
+ exist.setLayoutParams(geo);
+ geo = geoOld;
+ }
+ src.view = new SrcCopy(context, exist);
+ ((SrcCopy) src.view).scaleX = 0;
+ } else {
+ src.view = new SrcVideo(context, isLive ? url : Util.curProgDir+"/"+id, source.intg("vol", 100) / 100.0f, dur, source.bool("useSW"), isLive);
+ videoMap.put(key, src.view);
+ }
+ } else if(src.type.equals("Audio")) {
+ if(id==null) continue;
+ src.view = new SrcVideo(context, Util.curProgDir + "/" +id, source.intg("vol", 100) / 100.0f, dur, false, false);
+ } else if(src.type.equals("Scroll")) {
+ JSList imgs = source.jslist("imgs");
+ if(imgs.isEmpty()) continue;
+ var images = new ArrayList();
+ for(var img : imgs) images.add(BitmapFactory.decodeFile(Util.curProgDir+"/"+img));
+ var directStr = source.str("direct");
+ src.view = new SrcScroll(this, images, directStr==null ? 0 : directStr.charAt(0), source.dbl("speed"));
+ src.text = source.str("tts");
+ if(src.text!=null) {
+ src.tts = new TextToSpeech(context, (int status)->{
+ Util.println("status: "+status+" "+(status==TextToSpeech.SUCCESS));
+ }, "com.iflytek.speechcloud");
+ src.tts.setSpeechRate((float) source.dbl("speechRate"));
+ }
+ } else if(src.type.startsWith("MultiPng") || src.type.equals("SplitText")) {
+ JSList imgs = source.jslist("arrayPics");
+ if(imgs.isEmpty()) continue;
+ var mode = source.str("curchange");
+ var hasTTS = src.type.endsWith("Audio");
+ var speechRate = (float) source.dbl("voiceRate");
+ if(mode!=null ? mode.endsWith("roll") : (imgs.size()==1 && imgs.get(0).intg("picDuration")==0)) {
+ var img = imgs.get(0);
+ var images = new ArrayList();
+ images.add(BitmapFactory.decodeFile(Util.curProgDir+"/"+img.stnn("id")));
+ var speed = img.dbl("scrollSpeed");
+ if(speed==0) {
+ var scrollDur = img.dbl("effectSpeed");
+ if(scrollDur!=0) speed = 1000 / scrollDur;
+ }
+ char direct = 0;
+ var directStr = img.str("effect");
+ if(directStr!=null && ! directStr.equals("no")) {
+ int idx = directStr.lastIndexOf(' ');
+ if(idx > -1) direct = directStr.charAt(idx+1);
+ }
+ src.view = new SrcScroll(this, images, direct, speed);
+ if(hasTTS) {
+ src.text = img.str("text");
+ if(src.text!=null) {
+ src.tts = new TextToSpeech(context, (int status)->{
+ Util.println("status: "+status+" "+(status==TextToSpeech.SUCCESS));
+ }, "com.iflytek.speechcloud");
+ src.tts.setSpeechRate(speechRate);
+ }
+ }
+ } else {
+ var ele0 = src;
+ for(var map : imgs) {
+ var picDur = map.intg("picDuration")*1000;
+ if(picDur==0) picDur = dur / imgs.size();
+ src.type = "Image";
+ if(src!=ele0) {
+ src.startTime = layer.srcs.get(layer.srcs.size()-1).endTime;
+ src.entryEff = ele0.entryEff;
+ src.exitEff = ele0.exitEff;
+ src.entryDur = ele0.entryDur;
+ src.exitDur = ele0.exitDur;
+ src.exitStart = ele0.exitStart;
+ src.isEntryRand = ele0.isEntryRand;
+ src.isExitRand = ele0.isExitRand;
+ src.alpha = ele0.alpha;
+ src.breathe = ele0.breathe;
+ src.blinkHalf = ele0.blinkHalf;
+ src.rotate = ele0.rotate;
+ src.scaleX = ele0.scaleX;
+ src.scaleY = ele0.scaleY;
+ }
+ src.endTime = src.startTime + picDur;
+ if(hasTTS) {
+ src.text = map.str("text");
+ if(src.text!=null) {
+ src.tts = new TextToSpeech(context, (int status)->{
+ Util.println("status: "+status+" "+(status==TextToSpeech.SUCCESS));
+ }, "com.iflytek.speechcloud");
+ src.tts.setSpeechRate(speechRate);
+ }
+ }
+ var imgView = new ImageView(context);
+ imgView.setImageURI(Uri.fromFile(new File(Util.curProgDir + "/" + map.stnn("id"))));
+ imgView.setScaleType(ImageView.ScaleType.FIT_XY);
+ src.view = imgView;
+ src.view.setVisibility(GONE);
+ src.view.setLayoutParams(geo);
+ box.addView(src.view);
+ layer.srcs.add(src);
+ src = new Source();
+ }
+ }
+ } else if(src.type.equals("DigitalClock")) src.view = new SrcDigitalClock(this, source);
+ else if(src.type.startsWith("DigitalClock")) src.view = new SrcDigiClock(this, source);
+ else if(src.type.equals("AnalogClock")) src.view = new SrcAnaClock(this, geo.width, geo.height, Util.curProgDir + "/" + id, source);
+ else if(src.type.equals("WebURL")) src.view = new SrcWeb(this, source);
+ else if(src.type.equals("Timer")) src.view = new SrcTimer(this, source);
+ else if(src.type.equals("Countdown")) src.view = new SrcCountdown(this, source);
+ else if(src.type.startsWith("Environ")) src.view = new SrcEnviron(this, source);
+ else if(src.type.startsWith("Weather")) src.view = new SrcWeather(context, source);
+ else if(src.type.startsWith("VistorSource")) src.view = new SrcVisitor(this, source);
+ else if(src.type.startsWith("MultiLineText")) src.view = new SrcSensor(this, source, geo.width, geo.height);
+ else if(src.type.startsWith("SingleLineText")) {
+ var webView = new WebView(context);
+ webView.getSettings().setJavaScriptEnabled(true);
+ webView.setVerticalScrollBarEnabled(false);
+ webView.setHorizontalScrollBarEnabled(false);
+ webView.setBackgroundColor(Color.TRANSPARENT);
+ webView.setInitialScale(100);
+ webView.setLayoutParams(new AbsLayout.LayoutParams(0, -geo.height, geo.width, geo.height));
+ var html = source.stnn("html");
+ var prefix = "";
+ var suffix = "";
+ var speed = source.dbl("speed");
+ if(speed!=0) speed = 1000.0 / speed;
+ var imgs = new ArrayList();
+ imgs.add(Bitmap.createBitmap(geo.width, geo.height, Bitmap.Config.ARGB_8888));
+ var view = new SrcScroll(this, imgs, 'l', speed);
+ src.view = view;
+ webView.addJavascriptInterface(new Object() {
+ @JavascriptInterface
+ public void setWidth(int width) {
+ MainActivity.ins.runOnUiThread(() -> {
+ var hhh = view.imgs.get(0).getHeight();
+ var www = width + hhh;
+ webView.getLayoutParams().width = www;
+ view.imgs.set(0, Bitmap.createBitmap(www, hhh, Bitmap.Config.ARGB_8888));
+ addView(webView);
+ var atimer = new CountDownTimer(500, 500) {
+ @Override
+ public void onTick(long millisUntilFinished) {}
+ @Override
+ public void onFinish() {
+ var canvas = new Canvas(view.imgs.get(0));
+ webView.draw(canvas);
+ webView.setVisibility(GONE);
+ view.freshCnt = view.cur = 0;
+ if(view.direct=='l') view.end = -(view.imgs.get(0).getWidth()-view.step);
+ else if(view.direct=='r') view.end = view.imgs.get(0).getWidth()-view.step;
+ view.invalidate();
+ }
+ };
+ if(isShown()) atimer.start();
+ else timer = atimer;
+ });
+ }
+ }, "java");
+ webView.loadDataWithBaseURL(null, prefix+html+suffix, "text/html", "UTF-8", null);
+ }
+ else continue;
+ if(src.view==null) continue;
+ src.view.setVisibility(GONE);
+ src.view.setLayoutParams(geo);
+ box.addView(src.view);
+ layer.srcs.add(src);
+ src = new Source();
+ }
+ if(bdEle!=null && bdStart < bdEnd) {
+ JSList geometry = border.jslist("geometry");
+ src.startTime = bdStart;
+ src.endTime = bdEnd;
+ src.rotate = (float) border.dbl("rotate");
+ src.view = bdEle;
+ src.view.setVisibility(GONE);
+ 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);
+ }
+ if(! layer.srcs.isEmpty()) page.layers.add(layer);
+ }
+ if(page.sDur==0) {
+ if(page.audioDur > 0) page.sDur = page.audioDur;
+ else continue;
+ }
+ page.tDur = page.sDur * page.repeatTimes;
+ for_layer: for(int ll=0; ll= page.sDur) {
+ if(layer.srcs.size() > 1) layer.srcs.remove(ss--);
+ else {
+ page.layers.remove(ll--);
+ continue for_layer;
+ }
+ } else if(src.endTime > page.sDur) src.endTime = page.sDur;
+ }
+ if(layer.dur > page.sDur) layer.dur = page.sDur;
+ if(layer.dur == page.sDur) layer.isLoop = false;
+ }
+ pages.add(page);
+ }
+ }
+
+ void release() {
+ try {
+ setVisibility(GONE);
+ View view;
+ for(int cc=0; cc=exitStart) {
+ if(ff-1 <= exitStart && (blinkHalf!=0 || breathe!=0)) view.setAlpha(alpha);
+ var fff = ff - exitStart;
+ if(fff > exitDur) fff = exitDur;
+ if(exitEff == Effect.EXPAND_HOR) setScaleX(1 - fff / (float) exitDur);
+ else if(exitEff == Effect.EXPAND_VER) setScaleY(1 - fff / (float) exitDur);
+ else if(exitEff == Effect.EXPAND_LEFT) {
+ var rate = fff / (float) exitDur;
+ setScaleX(1 - rate);
+ view.setTranslationX(rate * w / 2);
+ } else if(exitEff == Effect.EXPAND_TOP) {
+ var rate = fff / (float) exitDur;
+ view.setScaleY(1 - rate);
+ view.setTranslationY(rate * h / 2);
+ } else if(exitEff == Effect.EXPAND_RIGHT) {
+ var rate = fff / (float) exitDur;
+ setScaleX(1 - rate);
+ view.setTranslationX(- rate * w / 2);
+ } else if(exitEff == Effect.EXPAND_BOTTOM) {
+ var rate = fff / (float) exitDur;
+ setScaleY(1 - rate);
+ view.setTranslationY(- rate * h / 2);
+ }
+ else if(exitEff == Effect.MOVE_LEFT) view.setTranslationX(- fff*w / (float) exitDur);
+ else if(exitEff == Effect.MOVE_RIGHT) view.setTranslationX(fff*w / (float) exitDur);
+ else if(exitEff == Effect.MOVE_TOP) view.setTranslationY(- fff*h / (float) exitDur);
+ else if(exitEff == Effect.MOVE_BOTTOM) view.setTranslationY(fff*h / (float) exitDur);
+ else if(exitEff == Effect.FADE) view.setAlpha(alpha - alpha * fff / exitDur);
+ else if(exitEff == Effect.ZOOM) {
+ setScaleX(1 - fff / (float) exitDur);
+ setScaleY(view.getScaleX());
+ } else if(exitEff == Effect.ZOOM_TL) {
+ var rate = fff / (float) exitDur;
+ view.setTranslationX(- rate * w / 2);
+ view.setTranslationY(- rate * h / 2);
+ setScaleX(1 - rate);
+ setScaleY(1 - rate);
+ } else if(exitEff == Effect.ZOOM_BR) {
+ var rate = fff / (float) exitDur;
+ view.setTranslationX(rate * w / 2);
+ view.setTranslationY(rate * h / 2);
+ setScaleX(1 - rate);
+ setScaleY(1 - rate);
+ } else if(exitEff == Effect.ZOOM_TR) {
+ var rate = fff / (float) exitDur;
+ view.setTranslationX(rate * w / 2);
+ view.setTranslationY(- rate * h / 2);
+ setScaleX(1 - rate);
+ setScaleY(1 - rate);
+ } else if(exitEff == Effect.ZOOM_BL) {
+ var rate = fff / (float) exitDur;
+ view.setTranslationX(- rate * w / 2);
+ view.setTranslationY(rate * h / 2);
+ setScaleX(1 - rate);
+ setScaleY(1 - rate);
+ } else if(exitEff == Effect.ROTATE) {
+ var rate = fff / (float) exitDur;
+ setScaleX(1 - rate);
+ setScaleY(view.getScaleX());
+ view.setRotation(rotate + rate * 360);
+ } else if(exitEff == Effect.ROTATE_R) {
+ var rate = fff / (float) exitDur;
+ setScaleX(1 - rate);
+ setScaleY(view.getScaleX());
+ view.setRotation(rotate - rate * 360);
+ }
+ } else {
+ resetEff();
+ if(blinkHalf!=0) view.setAlpha((ff / blinkHalf & 1) == 0 ? alpha : 0);
+ else if(breathe!=0) view.setAlpha((float) (Math.cos(ff%breathe*2*Math.PI/breathe)/2+0.5));
+ }
+ ff++;
+ }
+ void resetEff() {
+ view.setTranslationX(0);
+ view.setTranslationY(0);
+ view.setAlpha(alpha);
+ view.setScaleX(scaleX);
+ view.setScaleY(scaleY);
+ view.setRotation(rotate);
+ }
+ void setScaleX(float val) {
+ view.setScaleX(scaleX*val);
+ }
+ void setScaleY(float val) {
+ view.setScaleY(scaleY*val);
+ }
+ }
+ public static class Layer {
+ ArrayList srcs = new ArrayList<>();
+ long endMilli = Long.MAX_VALUE;
+ int dur;
+ boolean isLoop;
+ }
+ public enum Effect {
+ EXPAND_HOR, EXPAND_VER, EXPAND_LEFT, EXPAND_TOP, EXPAND_RIGHT, EXPAND_BOTTOM,
+ ZOOM, ZOOM_TL, ZOOM_TR, ZOOM_BR, ZOOM_BL,
+ ROTATE, ROTATE_R,
+ FADE,
+ MOVE_LEFT, MOVE_TOP, MOVE_RIGHT, MOVE_BOTTOM,
+ }
+ public static class Sche {
+ long startDate = -1, endDate = -1;
+ int startTime = -1, endTime = -1;
+ HashSet weeks;
+ }
+
+ public static class Page {
+ String name;
+ ArrayList layers = new ArrayList<>();
+ ArrayList sches;
+ long endMilli = Long.MAX_VALUE;
+ int sDur, tDur, repeatTimes, audioDur;
+
+ void hide() {
+ for(var layer : layers) for(var src : layer.srcs) src.hide();
+ }
+
+ void setMillis(long start, long cur) {
+ endMilli = start + sDur;
+ for(var layer : layers) {
+ if(layer.isLoop) layer.endMilli = start + layer.dur;
+ for(var src : layer.srcs) {
+ src.endMilli = start + src.endTime;
+ src.startMilli = start + src.startTime;
+ if(src.startTime == 0) {
+ if(src.view instanceof SrcVideo) ((SrcVideo)src.view).seekTo = cur - src.startMilli;
+ src.show();
+ }
+ }
+ }
+ }
+ void showHideSrcs(long milli) {
+ for(var layer : 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) {
+ if(src.view instanceof SrcVideo) ((SrcVideo)src.view).seekTo = 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();
+ }
+ }
+ }
+ }
+ boolean isScheOn(long milli) {
+ if(sches==null) return false;
+ var local = milli + TimeZone.getDefault().getOffset(milli);
+ int week = -1;
+ for(var sche : sches) {
+ if(notInRange(sche.startDate, local, sche.endDate)) continue;
+ if(notInRange(sche.startTime, local % 86400000L, sche.endTime)) continue;
+ if(sche.weeks==null) return true;
+ if(week==-1) week = ((int)(local / 86400000L) + 4) % 7;
+ if(sche.weeks.contains(week)) return true;
+ }
+ return false;
+ }
+ public boolean notInRange(long start, long val, long end) {
+ if(end==-1) return false;
+ return start <= end ? !(val >= start && val < end) : val >= end && val < start;
+ }
+ SimpleDateFormat dateFmt = new SimpleDateFormat("yyyy-MM-dd");
+ SimpleDateFormat timeFmt = new SimpleDateFormat("HH:mm:ss");
+ {
+ var zone = new SimpleTimeZone(0, "0");
+ dateFmt.getCalendar().setTimeZone(zone);
+ timeFmt.getCalendar().setTimeZone(zone);
+ }
+ public void parse(JSList schedules) {
+ if(schedules!=null) for(var schedule : schedules) {
+ var sche = new Sche();
+ var startTime = schedule.str("startTime");
+ if(startTime!=null) {
+ if(startTime.length()<=5) startTime += ":00";
+ try {
+ sche.startTime = (int)timeFmt.parse(startTime).getTime();
+ Util.println("startTime "+timeFmt.format(sche.startTime)+" "+sche.startTime);
+ } catch (ParseException e) {
+ Util.printStackTrace(e);
+ }
+ }
+ var endTime = schedule.str("endTime");
+ if(endTime!=null) {
+ if(endTime.length()<=5) endTime += ":00";
+ try {
+ sche.endTime = (int)timeFmt.parse(endTime).getTime();
+ Util.println("endTime "+timeFmt.format(sche.endTime)+" "+sche.endTime);
+ } catch (ParseException e) {
+ Util.printStackTrace(e);
+ }
+ }
+ if(sche.startTime==sche.endTime) sche.startTime = sche.endTime = -1;
+ var startDate = schedule.str("startDate");
+ if(startDate!=null) {
+ try {
+ sche.startDate = dateFmt.parse(startDate).getTime();
+ } catch (ParseException e) {
+ Util.printStackTrace(e);
+ }
+ }
+ var endDate = schedule.str("endDate");
+ if(endDate!=null) {
+ try {
+ sche.endDate = dateFmt.parse(endDate).getTime() + 86400000L;
+ } catch (ParseException e) {
+ Util.printStackTrace(e);
+ }
+ }
+ if(sche.startDate==sche.endDate) sche.startDate = sche.endDate = -1;
+ JSList weekFilter = schedule.jslist("weekFilter");
+ if(weekFilter!=null && ! weekFilter.isEmpty() && weekFilter.size() < 7) {
+ sche.weeks = new HashSet<>();
+ for(var week : weekFilter) sche.weeks.add(week.intValue() % 7);
+ }
+ if(sches==null) sches = new ArrayList<>();
+ sches.add(sche);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/SrcAnaClock.java b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/SrcAnaClock.java
new file mode 100644
index 0000000..a48e0f3
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/SrcAnaClock.java
@@ -0,0 +1,166 @@
+package com.xixun.xixunplayer;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.view.Choreographer;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import gnph.util.JSMap;
+
+@SuppressLint("ViewConstructor")
+public class SrcAnaClock extends View implements Choreographer.FrameCallback {
+
+ Calendar calendar;
+ Bitmap img;
+ int pinHourColor, pinMinColor, pinSecColor;
+ Path hPath, mPath, sPath;
+ float hAngle, mAngle, sAngle;
+ Paint paintPin = new Paint(), paint = new Paint();
+ boolean showSecHand;
+
+ public SrcAnaClock(Prog prog, float w, float h, String path, JSMap source) {
+ super(prog.getContext());
+ var timeZoneStr = source.str("timeZone");
+ //if(timeZoneStr!=null) timeZone = ZoneId.of(timeZoneStr);
+ var timeZone = timeZoneStr==null ? null : TimeZone.getTimeZone(timeZoneStr);
+ calendar = timeZone==null ? Calendar.getInstance() : Calendar.getInstance(timeZone);
+
+ var sideLen = Math.min(w, h);
+ var halfSide = sideLen / 2;
+ var lineWidth = sideLen / 128;
+ if(lineWidth < 1) lineWidth = 1;
+
+ paintPin.setAntiAlias(true);
+ paintPin.setStyle(Paint.Style.FILL_AND_STROKE);
+ paintPin.setStrokeWidth(lineWidth);
+ paintPin.setStrokeJoin(Paint.Join.ROUND);
+ paint.setAntiAlias(true);
+ paint.setStyle(Paint.Style.FILL);
+ var fontSize = sideLen / 11;
+ paint.setTextSize(fontSize);
+
+ img = BitmapFactory.decodeFile(path);
+ if(img==null) {
+ img = Bitmap.createBitmap((int)w, (int)h, Bitmap.Config.ARGB_8888);
+ var canvas = new Canvas(img);
+ canvas.translate(w / 2, h / 2);
+ if(source.bool("showBg")) {
+ var bgColor = Color.parseColor(source.stnn("bgColor"));
+ var opacity = source.dbl("opacity", -1);
+ if(opacity!=-1) bgColor = bgColor & 0x00ffffff | ((int)opacity*255)<<24;
+ paint.setColor(bgColor);
+ canvas.drawOval(-halfSide, -halfSide, halfSide, halfSide, paint);
+ }
+ var minMarkSize = sideLen/48;
+ var hourMarkSize = sideLen/32;
+ var scaleHourColor = Color.parseColor(source.stnn("scaleHourColor"));
+ var scaleMinColor = Color.parseColor(source.stnn("scaleMinColor"));
+ var showScaleNum = source.bool("showScaleNum");
+ var rScale = (sideLen - Math.max(minMarkSize, hourMarkSize)) / 2;
+ var rNum = halfSide * 0.83;
+ for(int i=0; i<60; i++) {
+ var k = i * Math.PI / 30;
+ var x = (float) (Math.sin(k) * rScale);
+ var y = (float) (-Math.cos(k) * rScale);
+ if(i % 5 > 0) {
+ var rr = minMarkSize / 2f;
+ paint.setColor(scaleMinColor);
+ canvas.drawOval(x-rr, y-rr, x+rr, y+rr, paint);
+ } else {
+ var rr = hourMarkSize / 2f;
+ paint.setColor(scaleHourColor);
+ canvas.drawOval(x-rr, y-rr, x+rr, y+rr, paint);
+ if(showScaleNum) {
+ var hour = i/5;
+ if(hour==0) hour = 12;
+ x = (float) (Math.sin(k) * rNum);
+ y = (float) (-Math.cos(k) * rNum);
+ canvas.drawText(String.valueOf(hour), x - fontSize*(hour<10 ? 0.28f : 0.6f), y + fontSize*0.355f, paint);
+ }
+ }
+ }
+ }
+ pinHourColor = Color.parseColor(source.stnn("pinHourColor"));
+ pinMinColor = Color.parseColor(source.stnn("pinMinColor"));
+ pinSecColor = Color.parseColor(source.stnn("pinSecColor"));
+ var hhLen = (float) source.dbl("pinHourLen", 50);
+ var mhLen = (float) source.dbl("pinMinLen", 75);
+ var shLen = (float) source.dbl("pinSecLen", 100);
+ var hhWidth = (float) source.dbl("pinHourWidth", 15);
+ var mhWidth = (float) source.dbl("pinMinWidth", 10);
+ var shWidth = (float) source.dbl("pinSecWidth", 5);
+ showSecHand = source.bool("showSecond");
+
+ var rx = hhWidth*sideLen/400;
+ hPath = new Path();
+ hPath.moveTo(rx, 0);
+ hPath.lineTo(0, hhLen*sideLen/-200);
+ hPath.lineTo(-rx, 0);
+ hPath.close();
+
+ rx = mhWidth*sideLen/400;
+ mPath = new Path();
+ mPath.moveTo(rx, 0);
+ mPath.lineTo(0, mhLen*sideLen/-200);
+ mPath.lineTo(-rx, 0);
+ mPath.close();
+ rx = shWidth*sideLen/400;
+ sPath = new Path();
+ sPath.moveTo(rx, 0);
+ sPath.lineTo(0, shLen*sideLen/-200);
+ sPath.lineTo(-rx, 0);
+ sPath.close();
+ prog.calls.add(this);
+ }
+
+ void cal() {
+ //var time = timeZone==null ? LocalTime.now() : LocalTime.now(timeZone);
+ calendar.setTimeInMillis(lastSec*1000);
+ sAngle = calendar.get(Calendar.SECOND) * 6;
+ mAngle = calendar.get(Calendar.MINUTE) * 6 + sAngle/60;
+ hAngle = calendar.get(Calendar.HOUR_OF_DAY) * 30 + mAngle/12;
+ }
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+ if(img != null) canvas.drawBitmap(img, null, new RectF(0,0, getWidth(), getHeight()), null);
+ canvas.translate(getWidth()/2f, getHeight()/2f);
+
+ paintPin.setColor(pinHourColor);
+ canvas.rotate(hAngle);
+ canvas.drawPath(hPath, paintPin);
+
+ paintPin.setColor(pinMinColor);
+ canvas.rotate(mAngle-hAngle);
+ canvas.drawPath(mPath, paintPin);
+ if(showSecHand) {
+ paintPin.setColor(pinSecColor);
+ canvas.rotate(sAngle-mAngle);
+ canvas.drawPath(sPath, paintPin);
+ }
+ }
+
+ long lastSec;
+
+ @Override
+ public void doFrame(long ms) {
+ if(! isShown()) return;
+ var sec = ms / 1000;
+ if(sec != lastSec) {
+ lastSec = sec;
+ cal();
+ invalidate();
+ }
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/SrcBorder.java b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/SrcBorder.java
new file mode 100644
index 0000000..10f6c60
--- /dev/null
+++ b/XixunPlayer-laun/app/src/main/java/com/xixun/xixunplayer/SrcBorder.java
@@ -0,0 +1,89 @@
+package com.xixun.xixunplayer;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.view.Choreographer;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+@SuppressLint("ViewConstructor")
+public class SrcBorder extends View implements Choreographer.FrameCallback {
+ Bitmap img;
+ int[] lens = new int[4];
+ Path[] paths = new Path[4];
+ int[] offs = new int[]{0,0,0,0};
+ byte eff;
+ int interval;
+
+ public SrcBorder(Prog prog, String path, String effStr, int speed) {
+ super(prog.getContext());
+ img = BitmapFactory.decodeFile(path);
+ if(effStr.startsWith("ro")) eff = 'r';
+ else if(effStr.startsWith("bl")) eff = 'b';
+ if(eff=='r') interval = speed==1 ? 4 : (speed==2 ? 2 : 1);
+ else interval = speed==1 ? 30 : (speed==2 ? 15 : 4);
+ if(eff!=0) prog.calls.add(this);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ lens[0] = lens[2] = w;
+ lens[1] = lens[3] = h;
+ int bdWidth = img.getHeight();
+ for(int i=0; i<2; i++) {
+ Path path = new Path();
+ path.moveTo(0,0);
+ path.lineTo(lens[i], 0);
+ path.lineTo(lens[i] - bdWidth, bdWidth);
+ path.lineTo(bdWidth, bdWidth);
+ path.close();
+ paths[i] = paths[i+2] = path;
+ }
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+ if(eff!='b' || offs[0] <= 0) {
+ int bdWidth = img.getHeight();
+ int bdLen = img.getWidth();
+ offs[1] = (offs[0] + lens[0]-bdWidth)%bdLen;
+ offs[2] = (offs[0] + lens[0]+lens[1]-bdWidth*2)%bdLen;
+ offs[3] = (offs[0] + lens[0]*2+lens[1]-bdWidth*3)%bdLen;
+ for(int ll=0; ll<4; ll++) {
+ canvas.save();
+ canvas.clipPath(paths[ll]);
+ for(int x=-offs[ll]; x";
+ suffix = "