diff --git a/XixunPlayer/.gitignore b/XixunPlayer/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/XixunPlayer/.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/.idea/.gitignore b/XixunPlayer/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/XixunPlayer/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/XixunPlayer/.idea/compiler.xml b/XixunPlayer/.idea/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/XixunPlayer/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer/.idea/gradle.xml b/XixunPlayer/.idea/gradle.xml
new file mode 100644
index 0000000..ae388c2
--- /dev/null
+++ b/XixunPlayer/.idea/gradle.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer/.idea/misc.xml b/XixunPlayer/.idea/misc.xml
new file mode 100644
index 0000000..8978d23
--- /dev/null
+++ b/XixunPlayer/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer/app/.gitignore b/XixunPlayer/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/XixunPlayer/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/XixunPlayer/app/build.gradle b/XixunPlayer/app/build.gradle
new file mode 100644
index 0000000..05d257e
--- /dev/null
+++ b/XixunPlayer/app/build.gradle
@@ -0,0 +1,41 @@
+plugins {
+ id 'com.android.application'
+}
+
+android {
+ namespace 'com.xixun.xixunplayer'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "com.xixun.xixunplayer"
+ minSdk 28
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ 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 files('libs\\gnph.jar')
+ implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.25'
+ implementation files('libs\\xixun_card_settings_1.2.4.jar')
+}
\ No newline at end of file
diff --git a/XixunPlayer/app/libs/gnph.jar b/XixunPlayer/app/libs/gnph.jar
new file mode 100644
index 0000000..49484bf
Binary files /dev/null and b/XixunPlayer/app/libs/gnph.jar differ
diff --git a/XixunPlayer/app/libs/xixun_card_settings_1.2.4.jar b/XixunPlayer/app/libs/xixun_card_settings_1.2.4.jar
new file mode 100644
index 0000000..0d17c22
Binary files /dev/null and b/XixunPlayer/app/libs/xixun_card_settings_1.2.4.jar differ
diff --git a/XixunPlayer/app/proguard-rules.pro b/XixunPlayer/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/XixunPlayer/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/app/release/output-metadata.json b/XixunPlayer/app/release/output-metadata.json
new file mode 100644
index 0000000..2af5b63
--- /dev/null
+++ b/XixunPlayer/app/release/output-metadata.json
@@ -0,0 +1,20 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "com.xixun.xixunplayer",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 1,
+ "versionName": "1.0",
+ "outputFile": "app-release.apk"
+ }
+ ],
+ "elementType": "File"
+}
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/AndroidManifest.xml b/XixunPlayer/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..43fd492
--- /dev/null
+++ b/XixunPlayer/app/src/main/AndroidManifest.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/aidl/com/xixun/util/PlayerInfo.aidl b/XixunPlayer/app/src/main/aidl/com/xixun/util/PlayerInfo.aidl
new file mode 100644
index 0000000..7726d8e
--- /dev/null
+++ b/XixunPlayer/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/app/src/main/java/com/xixun/xixunplayer/BackView.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/BackView.java
new file mode 100644
index 0000000..aecdd17
--- /dev/null
+++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/BackView.java
@@ -0,0 +1,30 @@
+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.os.Environment;
+import android.view.View;
+
+public class BackView extends View {
+
+ public Bitmap img, cosImg;
+ public int width, height;
+
+ public BackView(Context context, int width, int height) {
+ super(context);
+ img = BitmapFactory.decodeResource(context.getResources(), R.drawable.back);
+ cosImg = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory() + "/XixunPlayer/background");
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if(cosImg!=null) canvas.drawBitmap(cosImg, new Rect(0, 0, cosImg.getWidth(), cosImg.getHeight()), new Rect(0, 0, width, height), null);
+ else if(img!=null) for(int y=0; y imgs = new HashMap<>();
+ ImageView yearComps[] = {new ImageView(getContext()), new ImageView(getContext()), new ImageView(getContext()), new ImageView(getContext())};
+ ImageView monthComps[] = {new ImageView(getContext()), new ImageView(getContext())};
+ ImageView dayComps[] = {new ImageView(getContext()), new ImageView(getContext())};
+ ImageView weekComp = new ImageView(getContext()), ampmComp = new ImageView(getContext());
+ ImageView hourComps[] = {new ImageView(getContext()), new ImageView(getContext())};
+ ImageView minComps[] = {new ImageView(getContext()), new ImageView(getContext())};
+ ImageView secComps[] = {new ImageView(getContext()), new ImageView(getContext())};
+
+ ZoneId timeZone;
+ String timeptn;
+ boolean multiline, weekly, isSingleMonth;
+
+ public EleDigiClock(String prefix, JSMap json, Context context) {
+ super(context);
+ setGravity(Gravity.CENTER);
+ timeZone = ZoneId.of(json.stnn("timeZone"));
+ var spaceWidth = json.dbl("spaceWidth");
+ JSList pics = json.jslist("arrayPics");
+ for(var pic : pics) imgs.put(pic.stnn("name"), BitmapFactory.decodeFile(prefix+pic.stnn("id")));
+ int dateStyle = json.intg("dateStyle");
+ isSingleMonth = dateStyle==1||dateStyle==2||dateStyle==4||dateStyle==6||dateStyle==8||dateStyle==10||dateStyle==12;
+ var timeSep = imgs.get("maohao");
+ weekly = json.bool("weekly");
+ var hour12 = json.bool("hour12");
+ var AmPm = hour12 ? json.bool("AmPm") : false;
+ timeptn = hour12 ? "hhmmssa" : "HHmmss";
+ var hour = json.bool("hour");
+ var min = json.bool("min");
+ var sec = json.bool("sec");
+ multiline = json.bool("multiline");
+ addStretch();
+ if(multiline) {
+ vertical();
+ var hBox = new LinearBox(this).horizontal();
+ hBox.addStretch();
+ addDate(dateStyle, json, hBox);
+ hBox.addStretch();
+ if(weekly) {
+ hBox = new LinearBox(this).horizontal();
+ hBox.addStretch();
+ hBox.addView(weekComp);
+ hBox.addStretch();
+ }
+ hBox = new LinearBox(this).horizontal();
+ hBox.addStretch();
+ if(AmPm) {
+ hBox.addView(ampmComp);
+ hBox.addSpacing((int)spaceWidth);
+ }
+ if(hour) {
+ hBox.addView(hourComps[0]);
+ hBox.addView(hourComps[1]);
+ }
+ if(hour&&min) hBox.addView(newImgView(timeSep));
+ if(min) {
+ hBox.addView(minComps[0]);
+ hBox.addView(minComps[1]);
+ }
+ if(min&&sec) hBox.addView(newImgView(timeSep));
+ if(sec) {
+ hBox.addView(secComps[0]);
+ hBox.addView(secComps[1]);
+ }
+ hBox.addStretch();
+ } else {
+ setOrientation(HORIZONTAL);
+ addDate(dateStyle, json, this);
+ if(getChildCount()>1) addSpacing((int)spaceWidth*2);
+ if(weekly) {
+ addView(weekComp);
+ addSpacing((int)spaceWidth*2);
+ }
+ if(AmPm) {
+ addView(ampmComp);
+ addSpacing((int)spaceWidth);
+ }
+ if(hour) {
+ addView(hourComps[0]);
+ addView(hourComps[1]);
+ }
+ if(hour&&min) addView(newImgView(timeSep));
+ if(min) {
+ addView(minComps[0]);
+ addView(minComps[1]);
+ }
+ if(min&&sec) addView(newImgView(timeSep));
+ if(sec) {
+ addView(secComps[0]);
+ addView(secComps[1]);
+ }
+ }
+ addStretch();
+ }
+
+ ImageView newImgView(Bitmap img) {
+ var imgv = new ImageView(getContext());
+ imgv.setImageBitmap(img);
+ return imgv;
+ }
+ void addDate(int dateStyle, JSMap layer, LinearLayout tar) {
+ if(dateStyle==0 || dateStyle==1) {
+ addYear(layer, tar, imgs.get("YEAR"));
+ if(layer.bool("month")) {
+ tar.addView(monthComps[0]);
+ tar.addView(monthComps[1]);
+ tar.addView(newImgView(imgs.get("MONTH")));
+ }
+ if(layer.bool("day")) {
+ tar.addView(dayComps[0]);
+ tar.addView(dayComps[1]);
+ tar.addView(newImgView(imgs.get("DAY")));
+ }
+ } else if(dateStyle==2 || dateStyle==3) {
+ var sep = imgs.get("xiegang");
+ if(layer.bool("month")) {
+ tar.addView(monthComps[0]);
+ tar.addView(monthComps[1]);
+ tar.addView(newImgView(sep));
+ }
+ if(layer.bool("day")) {
+ tar.addView(dayComps[0]);
+ tar.addView(dayComps[1]);
+ tar.addView(newImgView(sep));
+ }
+ addYear(layer, tar, null);
+ } else if(dateStyle==4 || dateStyle==5) {
+ var sep = imgs.get("xiegang");
+ if(layer.bool("day")) {
+ tar.addView(dayComps[0]);
+ tar.addView(dayComps[1]);
+ tar.addView(newImgView(sep));
+ }
+ if(layer.bool("month")) {
+ tar.addView(monthComps[0]);
+ tar.addView(monthComps[1]);
+ tar.addView(newImgView(sep));
+ }
+ addYear(layer, tar, null);
+ } else if(dateStyle==6 || dateStyle==7) {
+ var sep = imgs.get("xiegang");
+ addYear(layer, tar, sep);
+ if(layer.bool("month")) {
+ tar.addView(monthComps[0]);
+ tar.addView(monthComps[1]);
+ tar.addView(newImgView(sep));
+ }
+ if(layer.bool("day")) {
+ tar.addView(dayComps[0]);
+ tar.addView(dayComps[1]);
+ }
+ } else if(dateStyle==8 || dateStyle==9) {
+ var sep = imgs.get("hengxian");
+ if(layer.bool("month")) {
+ tar.addView(monthComps[0]);
+ tar.addView(monthComps[1]);
+ tar.addView(newImgView(sep));
+ }
+ if(layer.bool("day")) {
+ tar.addView(dayComps[0]);
+ tar.addView(dayComps[1]);
+ tar.addView(newImgView(sep));
+ }
+ addYear(layer, tar, null);
+ } else if(dateStyle==10 || dateStyle==11) {
+ var sep = imgs.get("hengxian");
+ if(layer.bool("day")) {
+ tar.addView(dayComps[0]);
+ tar.addView(dayComps[1]);
+ tar.addView(newImgView(sep));
+ }
+ if(layer.bool("month")) {
+ tar.addView(monthComps[0]);
+ tar.addView(monthComps[1]);
+ tar.addView(newImgView(sep));
+ }
+ addYear(layer, tar, null);
+ } else if(dateStyle==12 || dateStyle==13) {
+ var sep = imgs.get("hengxian");
+ addYear(layer, tar, sep);
+ if(layer.bool("month")) {
+ tar.addView(monthComps[0]);
+ tar.addView(monthComps[1]);
+ tar.addView(newImgView(sep));
+ }
+ if(layer.bool("day")) {
+ tar.addView(dayComps[0]);
+ tar.addView(dayComps[1]);
+ }
+ }
+ }
+
+ void addYear(JSMap layer, LinearLayout tar, Bitmap sep) {
+ if(layer.bool("year")) {
+ if(layer.bool("fullYear")) {
+ tar.addView(yearComps[0]);
+ tar.addView(yearComps[1]);
+ }
+ tar.addView(yearComps[2]);
+ tar.addView(yearComps[3]);
+ if(sep != null) tar.addView(newImgView(sep));
+ }
+ }
+
+ void cal() {
+ var dt = LocalDateTime.now(timeZone);
+ var time = dt.toLocalTime();
+ var hms = Dates.fmt(time, timeptn);
+ ampmComp.setImageBitmap(imgs.get(time.getHour()<12?"AM":"PM"));
+ hourComps[0].setImageBitmap(imgs.get(hms.substring(0,1)));
+ hourComps[1].setImageBitmap(imgs.get(hms.substring(1,2)));
+ minComps[0].setImageBitmap(imgs.get(hms.substring(2,3)));
+ minComps[1].setImageBitmap(imgs.get(hms.substring(3,4)));
+ secComps[0].setImageBitmap(imgs.get(hms.substring(4,5)));
+ secComps[1].setImageBitmap(imgs.get(hms.substring(5,6)));
+ if(yearComps[0].getDrawable()==null || (time.getHour()==0 && time.getSecond()==0)) {
+ var date = dt.toLocalDate();
+ if(weekly) weekComp.setImageBitmap(imgs.get(weeks[date.getDayOfWeek().ordinal()]));
+ var ymd = Dates.fmt(date, "yyyyMMdd");
+ yearComps[0].setImageBitmap(imgs.get(ymd.substring(0,1)));
+ yearComps[1].setImageBitmap(imgs.get(ymd.substring(1,2)));
+ yearComps[2].setImageBitmap(imgs.get(ymd.substring(2,3)));
+ yearComps[3].setImageBitmap(imgs.get(ymd.substring(3,4)));
+ monthComps[0].setImageBitmap(isSingleMonth && ymd.charAt(4)=='0' ? null : imgs.get(ymd.substring(4,5)));
+ monthComps[1].setImageBitmap(imgs.get(ymd.substring(5,6)));
+ dayComps[0].setImageBitmap(isSingleMonth && ymd.charAt(6)=='0' ? null : imgs.get(ymd.substring(6,7)));
+ dayComps[1].setImageBitmap(imgs.get(ymd.substring(7,8)));
+ }
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if(visibility==View.VISIBLE) {
+ if(lastSec==0) {
+ cal();
+ choreographer.postFrameCallback(this);
+ }
+ }
+ }
+
+ Choreographer choreographer = Choreographer.getInstance();
+ long lastSec = 0;
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ if(! isShown()) {
+ lastSec = 0;
+ return;
+ }
+ var sec = System.currentTimeMillis() / 1000;
+ if(sec != lastSec) {
+ lastSec = sec;
+ cal();
+ invalidate();
+ }
+ choreographer.postFrameCallback(this);
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/EleEnviron.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/EleEnviron.java
new file mode 100644
index 0000000..f519025
--- /dev/null
+++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/EleEnviron.java
@@ -0,0 +1,192 @@
+package com.xixun.xixunplayer;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.view.Choreographer;
+import android.view.View;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import gnph.util.JSList;
+import gnph.util.JSMap;
+import gnph.util.NumFmts;
+import gnph.util.Sys;
+
+public class EleEnviron extends View implements Choreographer.FrameCallback {
+ class Item {
+ String key;
+ Bitmap lable;
+ ArrayList nums = new ArrayList<>();
+ Bitmap unit;
+
+ public Item(String key, Bitmap lable, Bitmap unit) {
+ this.key = key;
+ this.lable = lable;
+ this.unit = unit;
+ }
+ }
+ static String directs[] = {"NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N"};
+ HashMap imgMap = new HashMap<>();
+ Bitmap title;
+ ArrayList- items = new ArrayList<>();
+ MainActivity act;
+ int spaceWidth;
+ int interval, cur, end, step;
+ boolean isScroll, isFirst = true;
+
+ public EleEnviron(String prefix, JSMap json, Context context) {
+ super(context);
+ act = (MainActivity) context;
+ spaceWidth = json.intg("spaceWidth");
+ isScroll = json.bool("bSingleScroll");
+ try {
+ setBackgroundColor(Color.parseColor(json.stnn("backColor")));
+ } catch (Exception ignored) {}
+ var values = json.jsmap("values");
+ JSList arrayPics = json.jslist("arrayPics");
+ if(values!=null) {
+ var entrys = values.entrySet();
+ for(var entry : entrys) imgMap.put(entry.getKey(), BitmapFactory.decodeFile(prefix+entry.getValue()));
+ JSList jitems = json.jslist("items");
+ for(var jitem : jitems) {
+ var unit = jitem.str("unit");
+ items.add(new Item(jitem.str("name"), BitmapFactory.decodeFile(prefix+jitem.stnn("label")), unit==null ? null : BitmapFactory.decodeFile(prefix+unit)));
+ }
+ } else {
+ for(var img : arrayPics) imgMap.put(img.str("name"), BitmapFactory.decodeFile(prefix+img.stnn("id")));
+ imgMap.put("-", imgMap.remove("minus_sign"));
+ title = imgMap.get("labeltitle");
+ if(json.bool("bTemperature")) items.add(new Item("temperature", imgMap.remove("labeltemperature"), imgMap.remove("unit_celsius")));
+ if(json.bool("bHumidity")) items.add(new Item("humidity", imgMap.remove("labelhumidity"), imgMap.remove("unit_humidity")));
+ if(json.bool("bNoise")) items.add(new Item("noise", imgMap.remove("labelnoise"), imgMap.remove("unit_noise")));
+ if(json.bool("bWindSpeed")) items.add(new Item("windSpeed", imgMap.remove("labelwindSpeed"), imgMap.remove("unit_windspeed")));
+ if(json.bool("bWindDirection")) items.add(new Item("windDirection", imgMap.remove("labelwindDirection"), null));
+ if(json.bool("bPM25")) items.add(new Item("pm2.5", imgMap.remove("labelpm25"), imgMap.get("unit_pm10")));
+ if(json.bool("bPM10")) items.add(new Item("pm10", imgMap.remove("labelpm10"), imgMap.get("unit_pm10")));
+ }
+ var scrollSpeed = json.dbl("scrollSpeed");
+ if(scrollSpeed==0) {
+ var scrollDur = json.dbl("iScrollSpeed");
+ if(scrollDur==0) return;
+ scrollSpeed = 1000 / scrollDur;
+ }
+ interval = step = 1;
+ if(scrollSpeed > 60) step = (int) Math.round(scrollSpeed/60);
+ else if(scrollSpeed < 60) interval = (int) Math.round(60/scrollSpeed);
+ }
+
+ public static Method method;
+ static {
+ try {
+ method = Intent.class.getMethod("getExtra", String.class, Object.class);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ public void onReceive(Intent intent) {
+ try {
+ for(var item : items) {
+ item.nums.clear();
+ if(item.unit==null) {
+ var num = intent.getIntExtra(item.key, -1);
+ if(num>=0 && num<=15) item.nums.add(imgMap.get(directs[num]));
+ else {
+ var img = imgMap.get("-");
+ item.nums.add(img);
+ item.nums.add(img);
+ }
+ } else {
+ var num = ((Number) method.invoke(intent, item.key, -999)).doubleValue();
+ var str = num==-999 || (num==-1 && ! item.key.endsWith("rature")) ? "--" : NumFmts.zz().format(num);
+ for(int cc=0; cc imgs = new ArrayList<>();
+ int picDur, EffDur, AniInterval, step, imgc, imgx, imgy;
+ char effType;
+ boolean needRand = false;
+
+ public EleFlip(String dirPre, JSList maps, Context context) {
+ super(context);
+ var map = maps.get(0);
+ picDur = map.intg("picDuration")*60;
+ if(picDur==0) return;
+ EffDur = map.intg("effectSpeed")*60;
+ for(var amap : maps) imgs.add(BitmapFactory.decodeFile(dirPre+amap.stnn("id")));
+ var effStr = map.str("effect");
+ if(effStr == null || effStr.equals("no")) EffDur = 0;
+ else if(effStr.endsWith("left")) effType = 'l';
+ else if(effStr.endsWith("top")) effType = 't';
+ else if(effStr.endsWith("right")) effType = 'r';
+ else if(effStr.endsWith("bottom")) effType = 'b';
+ else if(effStr.equals("random")) needRand = true;
+ else EffDur = 0;
+ }
+
+ void startMove() {
+ if(EffDur==0) return;
+ if(needRand) effType = effTypes[rand.nextInt(4)];
+ double effDurD = EffDur;
+ if(effType=='l') {
+ imgx = getWidth();
+ imgy = 0;
+ effDurD /= getWidth();
+ } else if(effType=='r') {
+ imgx = -getWidth();
+ imgy = 0;
+ effDurD /= getWidth();
+ } else if(effType=='t') {
+ imgx = 0;
+ imgy = getHeight();
+ effDurD /= getHeight();
+ } else if(effType=='b') {
+ imgx = 0;
+ imgy = -getHeight();
+ effDurD /= getHeight();
+ } else return;
+ AniInterval = (int) Math.round(effDurD);
+ if(AniInterval < 1) AniInterval = 1;
+ step = (int) Math.round(AniInterval / effDurD);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if(freshCnt==0 && (EffDur!=0 || imgs.size()>1)) {
+ startMove();
+ choreographer.postFrameCallback(this);
+ }
+ canvas.drawBitmap(imgs.get(imgc), imgx, imgy, null);
+ }
+
+ Choreographer choreographer = Choreographer.getInstance();
+ int freshCnt, curDur;
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ if(! isShown()) {
+ freshCnt = curDur = imgc = 0;
+ return;
+ }
+ if(curDur < picDur) {
+ curDur++;
+ if(AniInterval > 0) {
+ if(freshCnt < AniInterval) freshCnt++;
+ else {
+ freshCnt = 1;
+ if(effType=='l') {
+ imgx -= step;
+ if(imgx < 0) imgx = 0;
+ } else if(effType=='t') {
+ imgy -= step;
+ if(imgy < 0) imgy = 0;
+ } else if(effType=='r') {
+ imgx += step;
+ if(imgx > 0) imgx = 0;
+ } else if(effType=='b') {
+ imgy += step;
+ if(imgy > 0) imgy = 0;
+ }
+ if(imgx==0 && imgy==0) AniInterval = 0;
+ invalidate();
+ }
+ }
+ } else {
+ curDur = 0;
+ freshCnt = 1;
+ if(imgc >= imgs.size()-1) imgc = 0;
+ else imgc++;
+ startMove();
+ invalidate();
+ }
+ choreographer.postFrameCallback(this);
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/EleScroll.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/EleScroll.java
new file mode 100644
index 0000000..2694390
--- /dev/null
+++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/EleScroll.java
@@ -0,0 +1,90 @@
+package com.xixun.xixunplayer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.view.Choreographer;
+import android.view.View;
+import android.view.ViewGroup;
+
+import gnph.util.JSMap;
+
+public class EleScroll extends View implements Choreographer.FrameCallback {
+
+ Bitmap img;
+ int interval, cur, end, step;
+ char effect;
+
+ public EleScroll(String dirPre, JSMap json, Context context) {
+ super(context);
+ img = BitmapFactory.decodeFile(dirPre + json.stnn("id"));
+ var effStr = json.str("effect");
+ if(effStr==null || effStr.equals("no")) return;
+ var scrollSpeed = json.dbl("scrollSpeed");
+ if(scrollSpeed==0) {
+ var scrollDur = json.dbl("effectSpeed");
+ if(scrollDur==0) return;
+ scrollSpeed = 1000 / scrollDur;
+ }
+ interval = step = 1;
+ if(scrollSpeed > 60) step = (int) Math.round(scrollSpeed/60);
+ else if(scrollSpeed < 60) interval = (int) Math.round(60/scrollSpeed);
+ int idx = effStr.lastIndexOf(' ');
+ if(idx > -1) {
+ effect = effStr.charAt(idx+1);
+ if(effect=='l') end = -(img.getWidth()-step);
+ else if(effect=='r') end = img.getWidth()-step;
+ else if(effect=='t') end = -(img.getHeight()-step);
+ else if(effect=='b') end = img.getHeight()-step;
+ }
+ }
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if(img==null) return;
+ try {
+ if(effect=='l') {
+ canvas.drawBitmap(img, cur, 0, null);
+ canvas.drawBitmap(img, cur+img.getWidth(), 0, null);
+ } else if(effect=='r') {
+ canvas.drawBitmap(img, cur, 0, null);
+ canvas.drawBitmap(img, cur-img.getWidth(), 0, null);
+ } else if(effect=='t') {
+ canvas.drawBitmap(img, 0, cur, null);
+ canvas.drawBitmap(img, 0, cur+img.getHeight(), null);
+ } else if(effect=='b') {
+ canvas.drawBitmap(img, 0, cur, null);
+ canvas.drawBitmap(img, 0, cur-img.getHeight(), null);
+ } else canvas.drawBitmap(img, 0, 0, null);
+ if(freshCnt==0 && effect!=0 && interval!=0) choreographer.postFrameCallback(this);
+ } catch (RuntimeException e) {
+ ((Page) getParent()).remove(this);
+ e.printStackTrace();
+ }
+ }
+
+ Choreographer choreographer = Choreographer.getInstance();
+ int freshCnt;
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ if(! isShown()) {
+ freshCnt = cur = 0;
+ return;
+ }
+ if(freshCnt < interval) freshCnt++;
+ else {
+ freshCnt = 1;
+ if(effect=='t' || effect=='l') {
+ if(cur <= end) cur -= end;
+ else cur -= step;
+ } else if(effect=='b' || effect=='r') {
+ if(cur >= end) cur -= end;
+ else cur += step;
+ }
+ invalidate();
+ }
+ choreographer.postFrameCallback(this);
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/EleTimer.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/EleTimer.java
new file mode 100644
index 0000000..6f971a9
--- /dev/null
+++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/EleTimer.java
@@ -0,0 +1,167 @@
+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.graphics.Typeface;
+import android.view.Choreographer;
+import android.view.View;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import gnph.util.Dates;
+import gnph.util.JSList;
+import gnph.util.JSMap;
+import gnph.util.NumFmts;
+
+public class EleTimer extends View implements Choreographer.FrameCallback {
+
+ HashMap imgMap = new HashMap<>();
+ ArrayList imgs = new ArrayList<>();
+ Bitmap text, day, hour, min, sec;
+ int spaceWidth, len;
+ Paint paint = new Paint();
+ long targetTime;
+ boolean isDown;
+ boolean isMultiline;
+ boolean hasDay;
+ boolean hasHour;
+ boolean hasMin;
+ boolean hasSec;
+
+ public EleTimer(String prefix, JSMap json, Context context) {
+ super(context);
+ var imgEntrys = json.jsmap("imgs").entrySet();
+ for(var imgEntry : imgEntrys) imgMap.put(imgEntry.getKey(), BitmapFactory.decodeFile(prefix+imgEntry.getValue()));
+ text = imgMap.get("text");
+ day = imgMap.get("day");
+ hour = imgMap.get("hour");
+ min = imgMap.get("min");
+ sec = imgMap.get("sec");
+ spaceWidth = (int) Math.round(json.dbl("spaceWidth"));
+ isDown = json.bool("isDown");
+ targetTime = Dates.milli(json.stnn("targetTime")) / 1000;
+ hasDay = json.bool("hasDay");
+ hasHour = json.bool("hasHour");
+ hasMin = json.bool("hasMin");
+ hasSec = json.bool("hasSec");
+ isMultiline = json.bool("isMultiline");
+ paint.setTextAlign(Paint.Align.CENTER);
+ try {
+ setBackgroundColor(Color.parseColor(json.stnn("backColor")));
+ } catch (Exception ignored) {}
+ }
+
+ void cal() {
+ var cur = System.currentTimeMillis() / 1000;
+ var secs = isDown ? targetTime - cur : cur - targetTime;
+ if(secs < 0) secs = 0;
+ len = 0;
+ imgs.clear();
+ if(text!=null && ! isMultiline) {
+ imgs.add(text);
+ imgs.add(null);
+ len += text.getWidth();
+ len += spaceWidth;
+ }
+ if(hasDay) {
+ var str = Long.toString(secs/86400);
+ for(int cc=0; cc{
+ if(! isShown()) {
+ player.pause();
+ player.seekTo(0);
+ }
+ if(vol!=1) player.setVolume(vol, vol);
+ setOnPreparedListener(null);
+ });
+ setOnErrorListener((MediaPlayer mp, int what, int extra)->{
+ Util.makeText(getContext(), "Media Error: "+getErrorName(what)+". "+getErrorName(extra)).show();
+ return true;
+ });
+ start();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
+ }
+
+ @Override
+ public void onVisibilityAggregated(boolean isVisible) {
+ super.onVisibilityAggregated(isVisible);
+ if(isVisible) {
+ if(! isPlaying()) start();
+ } else {
+ pause();
+ seekTo(0);
+ }
+ }
+
+ static String getErrorName(int code) {
+ if(code==MediaPlayer.MEDIA_ERROR_UNKNOWN) return "UNKNOWN";
+ if(code==MediaPlayer.MEDIA_ERROR_SERVER_DIED) return "SERVER_DIED";
+ if(code==MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) return "NOT_VALID_FOR_PROGRESSIVE_PLAYBACK";
+ if(code==MediaPlayer.MEDIA_ERROR_IO) return "IO";
+ if(code==MediaPlayer.MEDIA_ERROR_MALFORMED) return "MALFORMED";
+ if(code==MediaPlayer.MEDIA_ERROR_UNSUPPORTED) return "UNSUPPORTED";
+ if(code==MediaPlayer.MEDIA_ERROR_TIMED_OUT) return "TIMED_OUT";
+ if(code==-2147483648) return "SYSTEM";
+ return "Unknown ("+code+")";
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainActivity.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainActivity.java
new file mode 100644
index 0000000..681f1a5
--- /dev/null
+++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/MainActivity.java
@@ -0,0 +1,413 @@
+package com.xixun.xixunplayer;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+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.RemoteException;
+import android.os.StatFs;
+import android.view.Choreographer;
+
+import androidx.activity.ComponentActivity;
+import androidx.annotation.RequiresApi;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.xixun.joey.aidlset.CardService;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import gnph.util.IOs;
+import gnph.util.JSList;
+import gnph.util.JSMap;
+import gnph.util.O;
+
+public class MainActivity extends ComponentActivity implements Choreographer.FrameCallback {
+
+ public static MainActivity ins;
+ public Intent environIntent = new Intent();
+ HashSet environs = new HashSet<>();
+ BackView backView;
+ ProgView progView;
+ int state;
+
+ @RequiresApi(api = Build.VERSION_CODES.R)
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ System.out.println("---- MainActivity onCreate ---- UI Thread: "+Thread.currentThread().getId());
+ ins = this;
+ if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) init();
+ else {
+ System.out.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, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if(requestCode==999 && grantResults!=null && grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED && backView==null) init();
+ }
+
+ public void init() {
+ Util.programDir = Environment.getExternalStorageDirectory() + "/XixunPlayer/program";
+ Util.backImgFile = Environment.getExternalStorageDirectory() + "/XixunPlayer/background";
+
+ var program = new File(Util.programDir);
+ if(program.isFile()) program.delete();
+ System.out.println("mkdir: "+program.mkdirs());
+
+ var aaafiles = new File(Environment.getExternalStorageDirectory()+"/XixunPlayer").listFiles();
+ if(aaafiles != null) for(var file : aaafiles) if(file.isFile() && ! file.getName().startsWith("background")) file.delete();
+
+ var conn = new ServiceConnection() {
+ public void onServiceDisconnected(ComponentName name) {
+ System.out.println("Disconnected cardsystem aidl service");
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder iBinder) {
+ System.out.println("Bind cardsystem aidl service success");
+ var service = CardService.Stub.asInterface(iBinder);
+ try {
+ backView = new BackView(MainActivity.this, service.getScreenWidth(), service.getScreenHeight());
+ state = 5;
+ initProg();
+ if(progView==null) setContentView(backView);
+ } catch (RemoteException e) {
+ Util.makeText(MainActivity.this, e.getMessage()).show();
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ var intent = new Intent("com.xixun.joey.aidlset.SettingsService");
+ intent.setPackage("com.xixun.joey.cardsystem");
+ bindService(intent, conn, Context.BIND_AUTO_CREATE);
+
+ registerReceiver(new BroadcastReceiver(){
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ System.out.println("Receive PAUSE_PLAYER");
+ if(intent.getBooleanExtra("pause", false)) {
+ state = 8;
+ if(progView!=null) {
+ progView.setVisibility(GONE);
+ progView = null;
+ setContentView(backView);
+ }
+ } else if(progView==null) initProg();
+ }
+ }, new IntentFilter("com.xixun.action.PAUSE_PLAYER"), RECEIVER_EXPORTED);
+
+// registerReceiver(new BroadcastReceiver(){
+// @Override
+// public void onReceive(Context context, Intent intent) {
+// }
+// }, new IntentFilter("com.xixun.joey.CHANGE_COMPANYID"), RECEIVER_EXPORTED);
+
+ registerReceiver(new BroadcastReceiver(){
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ MainActivity.this.environIntent = intent;
+ for(var environ : environs) {
+ environ.onReceive(intent);
+ environ.invalidate();
+ }
+ }
+ }, new IntentFilter("xixun.intent.action.TEMPERATURE_HUMIDITY"), RECEIVER_EXPORTED);
+
+ new Thread(()->{
+ try {
+ var serverSocket = new ServerSocket(3333);
+ while(true) {
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ System.out.println("Accept ...");
+ final var socket = serverSocket.accept();
+ System.out.println("Receiving ...");
+ in = socket.getInputStream();
+ out = socket.getOutputStream();
+ JSList hases = null;
+ label:
+ while(true) {
+ var obj = JSMap.from(in);
+ var _type = obj.stnn("_type");
+ System.out.println("_type: "+_type);
+ switch(_type) {
+ case "consult":
+ JSList ids = obj.jslist("idList");
+ hases = new JSList<>();
+ if(ids!=null) for(int i=0; i {
+ System.out.println("removeAllViews ...");
+ if(progView!=null) {
+ progView.setVisibility(GONE);
+ progView = null;
+ setContentView(backView);
+ }
+ latch.countDown();
+ });
+ var files = new File(Util.programDir).listFiles();
+ if(files == null) return;
+ Arrays.sort(files, (f1, f2) -> (int) (f1.lastModified() - f2.lastModified()));
+ try {
+ latch.await();
+ } catch (InterruptedException ignored) {}
+ for(var file : files) {
+ if(hases!=null && hases.contains(file.getName())) continue;
+ var len = file.length();
+ if(file.delete()) {
+ remain += len;
+ if(remain>=0) break;
+ }
+ }
+ }
+ break;
+ case "fileStart":
+ var size = obj.intg("size");
+ var name = obj.stnn("id");
+ System.out.println(" size: " + size + " name: " + name);
+ var fout = new FileOutputStream(Util.programDir + "/" + name);
+ IOs.write(fout, in, size);
+ fout.flush();
+ fout.getFD().sync();
+ fout.close();
+ break;
+ case "imgFileStart":
+ size = obj.intg("size");
+ fout = new FileOutputStream(Util.backImgFile);
+ IOs.write(fout, in, size);
+ fout.flush();
+ fout.getFD().sync();
+ fout.close();
+ runOnUiThread(() -> {
+ backView.cosImg = BitmapFactory.decodeFile(Util.backImgFile);
+ backView.invalidate();
+ });
+ break;
+ case "imgFileEnd":
+ new JSMap("success", true).writeClose(out);
+ break label;
+ case "proEnd":
+ new JSMap("success", true).writeClose(out);
+ runOnUiThread(this::initProg);
+ break label;
+ case "DelPrograms":
+ var latch = new CountDownLatch(1);
+ AtomicBoolean ok = new AtomicBoolean(false);
+ runOnUiThread(() -> {
+ ok.set(delProgFile());
+ latch.countDown();
+ });
+ try {
+ latch.await();
+ } catch (InterruptedException ignored) {}
+ new JSMap("success", ok.get()).writeClose(out);
+ break label;
+ case "DelBackImg":
+ MainActivity.ins.runOnUiThread(() -> {
+ MainActivity.ins.backView.cosImg = null;
+ MainActivity.ins.backView.invalidate();
+ });
+ new JSMap("success", new File(Util.backImgFile).delete()).writeClose(out);
+ break label;
+ case "getPlayerState":
+ var descs = Util.getState(state);
+ new JSMap("code", state, "des_en", descs[0], "des", descs[1]).writeClose(out);
+ break label;
+ }
+ }
+ } catch (Throwable e) {
+ MainActivity.ins.runOnUiThread(() -> {
+ Util.makeText(MainActivity.this, e.getMessage()).show();
+ });
+ e.printStackTrace();
+ } finally {
+ O.close(in, out);
+ }
+ }
+ } catch (Throwable e) {
+ MainActivity.ins.runOnUiThread(() -> {
+ Util.makeText(MainActivity.this, e.getMessage()).show();
+ });
+ e.printStackTrace();
+ }
+ }).start();
+ }
+
+ public boolean delProgFile() {
+ if(progView!=null) {
+ progView.setVisibility(GONE);
+ progView = null;
+ setContentView(backView);
+ }
+ 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 ProgView(task, backView.width, backView.height, this);
+ if(view.getChildCount()==0) {
+ state = 3;
+ return;
+ }
+ if(progView!=null) progView.setVisibility(GONE);
+ progView = view;
+ setContentView(progView);
+ syncProg((System.currentTimeMillis()+999)/1000*1000);
+ choreographer.postFrameCallback(this);
+ } catch (FileNotFoundException e) {
+ state = 3;
+ e.printStackTrace();
+ } catch (Throwable e) {
+ state = 7;
+ e.printStackTrace();
+ }
+ }
+
+ Choreographer choreographer = Choreographer.getInstance();
+ ArrayList avas = new ArrayList<>();
+ int curAva, curTimes = 1;
+ long syncMilli = Long.MAX_VALUE;
+
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ if(progView == null) return;
+ var milli = System.currentTimeMillis();
+ var lastPage = page(curAva);
+ if(milli >= syncMilli) {
+ lastPage.setVisibility(GONE);
+ if(lastPage.hasVideo && progView.getChildCount()>1) lastPage.setLeft(lastPage.getRight()+1);
+ syncProg(milli-syncMilli>=1000 ? milli : syncMilli);
+ } else if(milli >= lastPage.endMilli) {
+ lastPage.setVisibility(GONE);
+ if(lastPage.hasVideo && curTimes==lastPage.repeatTimes && progView.getChildCount()>1) lastPage.setLeft(lastPage.getRight()+1);
+ if(curTimes < lastPage.repeatTimes) curTimes++;
+ else {
+ curTimes = 1;
+ curAva++;
+ if(curAva >= avas.size()) {
+ syncProg(milli-lastPage.endMilli>=1000 ? milli : lastPage.endMilli);
+ choreographer.postFrameCallback(this);
+ return;
+ }
+ }
+ page(curAva).setMillis(lastPage.endMilli);
+ } else {
+ for(var layer : lastPage.layers) {
+ for(var ele : layer.eles) if(ele.isShort) {
+ if(ele.view.getVisibility()!=VISIBLE) {
+ if(milli < ele.endMilli && milli >= ele.startMilli) ele.view.setVisibility(VISIBLE);
+ } else {
+ if(milli >= ele.endMilli) ele.view.setVisibility(GONE);
+ }
+ }
+ if(milli >= layer.endMilli) {
+ layer.endMilli += layer.dur;
+ for(var ele : layer.eles) {
+ ele.endMilli += layer.dur;
+ ele.startMilli += layer.dur;
+ if(ele.view.getVisibility()!=VISIBLE) {
+ if(milli < ele.endMilli && milli >= ele.startMilli) ele.view.setVisibility(VISIBLE);
+ } else {
+ if(milli >= ele.endMilli) ele.view.setVisibility(GONE);
+ }
+ }
+ }
+ }
+ }
+ choreographer.postFrameCallback(this);
+ }
+
+ void syncProg(long milli) {
+ curTimes = 1;
+ var dur = calAvas(milli);
+ if(dur==0) {
+ syncMilli = milli + 1000;
+ if(state!=2) {
+ setContentView(backView);
+ state = 2;
+ }
+ } else {
+ var page = page(curAva = 0);
+ syncMilli = milli / dur * dur + dur;
+ if(syncMilli - milli >= 2000) page.setMillis(milli);
+ if(state != 6) {
+ setContentView(progView);
+ state = 6;
+ }
+ }
+ }
+ int calAvas(long milli) {
+ avas.clear();
+ var dur = 0;
+ Page page;
+ for(int i=0; i eles = new ArrayList<>();
+ long endMilli = Long.MAX_VALUE;
+ int dur;
+ boolean repeat;
+ }
+
+ public static class Sche {
+ long startDate = -1, endDate = -1;
+ int startTime = -1, endTime = -1;
+ JSList weeks;
+ }
+
+ ArrayList layers = new ArrayList<>();
+ ArrayList sches;
+ long endMilli = Long.MAX_VALUE;
+ int dur, repeatTimes;
+ boolean hasVideo = false;
+
+ public Page(Context context) {
+ super(context);
+ }
+
+ public void setMillis(long milli) {
+ endMilli = milli + dur;
+ for(var layer : layers) {
+ if(layer.repeat) layer.endMilli = milli + layer.dur;
+ for(var ele : layer.eles) if(ele.isShort) {
+ if(ele.startTime > 0) {
+ ele.startMilli = milli + ele.startTime;
+ ele.view.setVisibility(GONE);
+ } else ele.view.setVisibility(VISIBLE);
+ ele.endMilli = milli + ele.endTime;
+ }
+ }
+ if(getLeft() != 0) setLeft(0);
+ setVisibility(VISIBLE);
+ }
+ public void remove(View view) {
+ view.setVisibility(GONE);
+ removeView(view);
+ for(int ll=0; ll 1) layer.eles.remove(ee);
+ else layers.remove(ll);
+ return;
+ }
+ }
+ }
+ public boolean isSchedule(long milli) {
+ if(sches==null) return true;
+ var local = milli + Dates.zoneOff;
+ var time = local % 86400000L;
+ var week = -1;
+ for(var sche : sches) {
+ if(notInRange(sche.startDate, local, sche.endDate)) continue;
+ if(notInRange(sche.startTime, time, sche.endTime)) continue;
+ if(sche.weeks==null) return true;
+ if(week==-1) week = LocalDate.ofEpochDay(local / 86400000L).getDayOfWeek().getValue();
+ 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;
+ }
+
+ public void parse(JSList schedules) {
+ if(schedules!=null) for(var schedule : schedules) {
+ var sche = new Sche();
+ var startTime = schedule.str("startTime");
+ if(startTime!=null) sche.startTime = LocalTime.parse(startTime).toSecondOfDay()*1000;
+ var endTime = schedule.str("endTime");
+ if(endTime!=null) sche.endTime = LocalTime.parse(endTime).toSecondOfDay()*1000;
+ if(sche.startTime==sche.endTime) sche.startTime = sche.endTime = -1;
+ var startDate = schedule.str("startDate");
+ if(startDate!=null) sche.startDate = LocalDate.parse(startDate).toEpochDay() * 86400000L;
+ var endDate = schedule.str("endDate");
+ if(endDate!=null) sche.endDate = (LocalDate.parse(endDate).toEpochDay() + 1) * 86400000L;
+ 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 = weekFilter;
+ if(sches==null) sches = new ArrayList<>();
+ sches.add(sche);
+ }
+ }
+}
diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/ProgView.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/ProgView.java
new file mode 100644
index 0000000..6d421c9
--- /dev/null
+++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/ProgView.java
@@ -0,0 +1,167 @@
+package com.xixun.xixunplayer;
+
+import android.content.Context;
+import android.net.Uri;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.AbsoluteLayout;
+import android.widget.ImageView;
+
+import java.io.File;
+
+import gnph.util.JSList;
+import gnph.util.JSMap;
+import pl.droidsonroids.gif.GifImageView;
+
+public class ProgView extends AbsoluteLayout {
+
+ public ProgView(JSMap task, int width, int height, Context context) {
+ super(context);
+ JSList pages = task.jslist("items");
+ for(var pageMap : pages) {
+ var _program = pageMap.jsmap("_program");
+ JSList layers = _program.jslist("layers");
+ if(layers.isEmpty()) continue;
+ var splitWidths = _program.jslist("splitWidths");
+ var page = new Page(context);
+ page.repeatTimes = pageMap.intg("repeatTimes");
+ page.parse(pageMap.jslist("schedules"));
+ for(int ll=layers.size()-1; ll>=0; ll--) {
+ var layer = new Page.Layer();
+ layer.repeat = layers.get(ll).bool("repeat");
+ JSList sources = layers.get(ll).jslist("sources");
+ var border = layers.get(ll).jsmap("border");
+ EleBorder bdEle = null;
+ int bdWidth = 0, bdStart = 0xffff, bdEnd = 0;
+ if(border!=null) {
+ bdEle = new EleBorder(Util.programDir+"/"+border.stnn("img"), border.stnn("eff"), border.intg("speed"), context);
+ bdWidth = bdEle.img.getHeight();
+ }
+ var ele = new Page.EleBase();
+ int x, y, w, h;
+ for(var source : sources) {
+ ele.type = source.stnn("_type");
+ if(ele.type.isEmpty()) continue;
+ var timeSpan = source.intg("timeSpan")*1000;
+ if(timeSpan==0) continue;
+ x = source.intg("left")+bdWidth;
+ y = source.intg("top")+bdWidth;
+ w = source.intg("width")-bdWidth-bdWidth;
+ h = source.intg("height")-bdWidth-bdWidth;
+ boolean notAudio = ! ele.type.equals("Audio");
+ if((w<=0 || h<=0) && notAudio) continue;
+ ele.startTime = source.intg("playTime")*1000;
+ if(bdStart > ele.startTime) bdStart = ele.startTime;
+ ele.endTime = ele.startTime + timeSpan;
+ if(bdEnd < ele.endTime) bdEnd = ele.endTime;
+ if(layer.dur < ele.endTime) layer.dur = ele.endTime;
+ if(page.dur < ele.endTime && notAudio) page.dur = ele.endTime;
+ ele.id = source.stnn("id");
+ ele.view = null;
+ if(ele.type.equals("Image")) {
+ ImageView imgView = source.stnn("fileExt").equalsIgnoreCase("gif") ? new GifImageView(context) : new ImageView(context);
+ imgView.setImageURI(Uri.fromFile(new File(Util.programDir+"/"+ele.id)));
+ imgView.setScaleType(ImageView.ScaleType.FIT_XY);
+ ele.view = imgView;
+ } else if(ele.type.equals("MultiPng")) {
+ JSList imgs = source.jslist("arrayPics");
+ if(imgs.isEmpty()) continue;
+ if(imgs.size()==1 && imgs.get(0).intg("picDuration")==0) ele.view = new EleScroll(Util.programDir+"/", imgs.get(0), context);
+ else ele.view = new EleFlip(Util.programDir+"/", imgs, context);
+ } else if(ele.type.equals("SplitText")) {
+// JSList imgs = source.jslist("arrayPics");
+// if(imgs.isEmpty()) continue;
+// ele.wgt = new View(context);
+// page.addView(ele.wgt, new AbsoluteLayout.LayoutParams(width, height, 0, 0));
+// var pheight = _program.intg("height");
+// if(imgs.size()==1 && imgs.get(0).intg("picDuration")==0) {
+// var wgt = new EleScroll(ele.wgt, dir+"/", imgs.get(0), context);
+// wgt->setGeometry(ele.x, ele.y, ele.w, ele.h);
+// for(int i=1; isetGeometry(ele.x, ele.y, splitWidths[i].toInt()-ele.x, ele.h);
+// wgt->splits.append(split);
+// }
+// } else {
+// auto wgt = new EleFlip(dir+"/", imgs, ele.wgt);
+// wgt->setGeometry(ele.x, ele.y, ele.w, ele.h);
+// for(int i=1; isetGeometry(ele.x, ele.y, splitWidths[i].toInt()-ele.x, ele.h);
+// wgt->splits.append(split);
+// }
+// }
+// ele.w = 0;
+ } else if(ele.type.equals("DigitalClockNew")) ele.view = new EleDigiClock(Util.programDir+"/", source, context);
+ else if(ele.type.equals("AnalogClock")) ele.view = new EleAnaClock(w, h, Util.programDir+"/"+ele.id, source, context);
+ else if(ele.type.equals("Video")) {
+ page.hasVideo = true;
+ var videoView = new EleVideo(Util.programDir + "/" + ele.id, context);
+ ele.view = videoView;
+ var vol = source.intg("vol", 100);
+ if (vol < 100) videoView.vol = vol / 100.0f;
+ } else if(ele.type.equals("Audio")) {
+ var videoView = new EleVideo(Util.programDir+"/"+ele.id, context);
+ ele.view = videoView;
+ var vol = source.intg("vol", 100);
+ if(vol<100) videoView.vol = vol/100.0f;
+ } else if(ele.type.equals("WebURL")) {
+ var webView = new WebView(context);
+ webView.setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ return false;
+ }
+ });
+ webView.loadUrl(source.stnn("url"));
+ ele.view = webView;
+ }
+ else if(ele.type.equals("Timer")) ele.view = new EleTimer(Util.programDir+"/", source, context);
+ else if(ele.type.equals("EnvironmentalMonitoring")) ele.view = new EleEnviron(Util.programDir+"/", source, context);
+ else continue;
+ if(ele.view==null) continue;
+ if(w>0) page.addView(ele.view, new AbsoluteLayout.LayoutParams(w, h, x, y));
+ layer.eles.add(ele);
+ ele = new Page.EleBase();
+ }
+ if(bdEle!=null && ! sources.isEmpty()) {
+ JSList geometry = border.jslist("geometry");
+ x = geometry.get(0).intValue();
+ y = geometry.get(1).intValue();
+ w = geometry.get(2).intValue();
+ h = geometry.get(3).intValue();
+ ele.startTime = bdStart;
+ ele.endTime = bdEnd;
+ ele.view = bdEle;
+ page.addView(ele.view, new AbsoluteLayout.LayoutParams(w, h, x, y));
+ layer.eles.add(ele);
+ }
+ if(! layer.eles.isEmpty()) page.layers.add(layer);
+ }
+ if(page.dur==0) continue;
+ for_layer: for(int ll=0; ll= page.dur) {
+ if(layer.eles.size() > 1) {
+ layer.eles.remove(ee--);
+ continue;
+ } else {
+ page.layers.remove(ll--);
+ continue for_layer;
+ }
+ } else if(ele.endTime > page.dur) ele.endTime = page.dur;
+ ele.isShort = ele.startTime > 0 || ele.endTime < page.dur;
+ }
+ if(layer.dur > page.dur) layer.dur = page.dur;
+ }
+ page.setVisibility(GONE);
+ addView(page, new AbsoluteLayout.LayoutParams(width, height, 0, 0));
+ }
+ }
+}
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Server.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Server.java
new file mode 100644
index 0000000..f58dbcf
--- /dev/null
+++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Server.java
@@ -0,0 +1,203 @@
+package com.xixun.xixunplayer;
+
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.xixun.util.PlayerInfo;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import gnph.util.IOs;
+import gnph.util.JSMap;
+import gnph.util.URLConn;
+
+public class Server extends Service {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+ PlayerInfo.Stub binder = new PlayerInfo.Stub() {
+ @Override
+ public String getProgramName() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public String getVersion() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void setScreenWidth(int w) throws RemoteException {
+
+ }
+
+ @Override
+ public void setScreenHeight(int h) throws RemoteException {
+
+ }
+
+ @Override
+ public void taskScreenshot(String cmdId) throws RemoteException {
+
+ }
+
+ @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 {
+ return null;
+ }
+
+ @Override
+ public void setUSBProgramPwd(String pwd) throws RemoteException {
+
+ }
+
+ @SuppressLint("ResourceType")
+ @Override
+ public String executeJosnCommand(String jsonstr) throws RemoteException {
+ System.out.println("Server executeJsonCommand ..."+jsonstr);//{"_type":"DeleteTask","id":"652522a0e81d1e000009201a","sendTo":"yzd-player"}
+ String id = null;
+ try {
+ var json = JSMap.from(jsonstr.getBytes(StandardCharsets.UTF_8));
+ var _type = json.stnn("_type");
+ id = json.stnn("id");
+ if(_type.equals("DeleteTask")) {
+ MainActivity.ins.runOnUiThread(() -> MainActivity.ins.delProgFile());
+ return new JSMap(
+ "_type", "DataCallback",
+ "result", "received command",
+ "cardId", Util.getCardId(),
+ "commandId", id
+ ).toString();
+ } else if(_type.equals("PlayerStateCommand")) {
+ var descs = Util.getState(MainActivity.ins.state);
+ return new JSMap(
+ "_type", "Success",
+ "cardId", Util.getCardId(),
+ "commandId", id,
+ "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 IOs.writeClose(new FileOutputStream(Util.backImgFile), new URLConn(url).in());
+ MainActivity.ins.runOnUiThread(() -> {
+ MainActivity.ins.backView.cosImg = url==null ? null : BitmapFactory.decodeFile(Util.backImgFile);
+ MainActivity.ins.backView.invalidate();
+ });
+ return new JSMap(
+ "_type", "Success",
+ "cardId", Util.getCardId(),
+ "commandId", id
+ ).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", id,
+ "img", img
+ ).toString();
+ }
+ return new JSMap(
+ "_type", "Error",
+ "errorMessage", "Unknow Type",
+ "cardId", Util.getCardId(),
+ "commandId", id
+ ).toString();
+ } catch (Exception e) {
+ return new JSMap(
+ "_type", "Error",
+ "errorMessage", e.toString(),
+ "cardId", Util.getCardId(),
+ "commandId", id
+ ).toString();
+ }
+ }
+
+ @Override
+ public void pausePlayer(boolean b) throws RemoteException {
+
+ }
+
+ @Override
+ public boolean isPause() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean clearTasks() throws RemoteException {
+ System.out.println("Server clearTasks ...");
+ return true;
+ }
+
+ @Override
+ public int countOfPrograms(int type) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void playInsertTask(String pid) throws RemoteException {
+
+ }
+
+ @Override
+ public void stopInsertTask(String pid) throws RemoteException {
+
+ }
+
+ @Override
+ public String getProgramTask() throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void setUploadLogUrl(String playLog) throws RemoteException {
+
+ }
+
+ @Override
+ public String getUploadLogUrl() throws RemoteException {
+ return null;
+ }
+ };
+}
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Util.java b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Util.java
new file mode 100644
index 0000000..b462e87
--- /dev/null
+++ b/XixunPlayer/app/src/main/java/com/xixun/xixunplayer/Util.java
@@ -0,0 +1,57 @@
+package com.xixun.xixunplayer;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+
+import gnph.util.IOs;
+
+public class Util {
+
+ public static final HashMap stateDescs = new HashMap<>();
+ static {
+ stateDescs.put(1, new String[]{"Initialize", "初始化"});
+ stateDescs.put(2, new String[]{"Schedules is over", "定时节目结束"});
+ stateDescs.put(3, new String[]{"No programs waiting to be played", "无待播放的节目"});
+ stateDescs.put(4, new String[]{"Delete program", "删除节目"});
+ stateDescs.put(5, new String[]{"Program processing", "处理节目中"});
+ stateDescs.put(6, new String[]{"Program Processed", "处理节目完成"});
+ stateDescs.put(7, new String[]{"Program maybe error", "节目可能有误"});
+ stateDescs.put(8, new String[]{"Screen-off", "关屏"});
+ stateDescs.put(9, new String[]{"Program's area hasn't arrived yet", "定点节目不在范围"});
+ }
+ public static final String[] stateDescsUnknow = {"Unknown", "未知"};
+
+ public static Toast makeText(Context context, CharSequence text) {
+ var toast = Toast.makeText(context, text, Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.TOP | Gravity.LEFT, 0, 0);
+ return toast;
+ }
+
+ public static String[] getState(int state) {
+ var descs = stateDescs.get(state);
+ return descs!=null ? descs : stateDescsUnknow;
+ }
+
+ public static String programDir, backImgFile;
+
+ public static String getCardId() {
+ try {
+ var bytes = IOs.readBytesClose(new FileInputStream(new File("/data/joey/signed/card.id")));
+ if(bytes.length < 40) return "";
+ byte[] cMyKey = new byte[]{97, 119, 38, 3, 46, 112, 36, 93, 58, 100, 103, 62, 115, 112, 114, 51, 43, 61, 2, 101, 119};
+ for(int i=0; i<20; ++i) bytes[i] = (byte) (bytes[i * 2] - cMyKey[i] - i - (bytes[i * 2 + 1] - 3));
+ var cardId = new String(bytes);
+ if(cardId.length() > 13) cardId = cardId.substring(0, 13);
+ return cardId;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+}
diff --git a/XixunPlayer/app/src/main/java/gnph/android/LinearBox.java b/XixunPlayer/app/src/main/java/gnph/android/LinearBox.java
new file mode 100644
index 0000000..f6606af
--- /dev/null
+++ b/XixunPlayer/app/src/main/java/gnph/android/LinearBox.java
@@ -0,0 +1,36 @@
+package gnph.android;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+public class LinearBox extends LinearLayout {
+
+ public LinearBox(Context context) {
+ super(context);
+ }
+ public LinearBox(ViewGroup box) {
+ super(box.getContext());
+ box.addView(this);
+ }
+
+ public LinearBox horizontal() {
+ setOrientation(HORIZONTAL);
+ return this;
+ }
+ public LinearBox vertical() {
+ setOrientation(VERTICAL);
+ return this;
+ }
+
+ public LinearBox addSpacing(int spacing) {
+ if(getOrientation()==HORIZONTAL) addView(new View(getContext()), new LayoutParams(spacing, 0));
+ else addView(new View(getContext()), new LayoutParams(0, spacing));
+ return this;
+ }
+ public LinearBox addStretch() {
+ addView(new View(getContext()), new LayoutParams(0, 0,1));
+ return this;
+ }
+}
diff --git a/XixunPlayer/app/src/main/res/drawable/back.png b/XixunPlayer/app/src/main/res/drawable/back.png
new file mode 100644
index 0000000..7d65f1e
Binary files /dev/null and b/XixunPlayer/app/src/main/res/drawable/back.png differ
diff --git a/XixunPlayer/app/src/main/res/drawable/ic_launcher_background.xml b/XixunPlayer/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/XixunPlayer/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/XixunPlayer/app/src/main/res/drawable/ic_launcher_foreground.xml b/XixunPlayer/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/XixunPlayer/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/XixunPlayer/app/src/main/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/XixunPlayer/app/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/XixunPlayer/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/XixunPlayer/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/XixunPlayer/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/XixunPlayer/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/XixunPlayer/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/XixunPlayer/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/XixunPlayer/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/XixunPlayer/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/XixunPlayer/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/XixunPlayer/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/XixunPlayer/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/XixunPlayer/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/XixunPlayer/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/XixunPlayer/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/XixunPlayer/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/XixunPlayer/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/XixunPlayer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/XixunPlayer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/XixunPlayer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/XixunPlayer/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/XixunPlayer/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/XixunPlayer/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/XixunPlayer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/XixunPlayer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/XixunPlayer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/XixunPlayer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/XixunPlayer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/XixunPlayer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/XixunPlayer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/XixunPlayer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/XixunPlayer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/XixunPlayer/app/src/main/res/values/colors.xml b/XixunPlayer/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c8524cd
--- /dev/null
+++ b/XixunPlayer/app/src/main/res/values/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/res/values/strings.xml b/XixunPlayer/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a619236
--- /dev/null
+++ b/XixunPlayer/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ XixunPlayer
+
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/res/values/themes.xml b/XixunPlayer/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..3a1eec4
--- /dev/null
+++ b/XixunPlayer/app/src/main/res/values/themes.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/res/xml/backup_rules.xml b/XixunPlayer/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/XixunPlayer/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer/app/src/main/res/xml/data_extraction_rules.xml b/XixunPlayer/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/XixunPlayer/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XixunPlayer/build.gradle b/XixunPlayer/build.gradle
new file mode 100644
index 0000000..3daed1d
--- /dev/null
+++ b/XixunPlayer/build.gradle
@@ -0,0 +1,4 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+id 'com.android.application' version '8.1.1' apply false
+}
\ No newline at end of file
diff --git a/XixunPlayer/gradle.properties b/XixunPlayer/gradle.properties
new file mode 100644
index 0000000..3e927b1
--- /dev/null
+++ b/XixunPlayer/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/XixunPlayer/gradle/wrapper/gradle-wrapper.jar b/XixunPlayer/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/XixunPlayer/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/XixunPlayer/gradle/wrapper/gradle-wrapper.properties b/XixunPlayer/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d587198
--- /dev/null
+++ b/XixunPlayer/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Oct 11 11:55:49 CST 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/XixunPlayer/gradlew b/XixunPlayer/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/XixunPlayer/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/XixunPlayer/gradlew.bat b/XixunPlayer/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/XixunPlayer/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/XixunPlayer/settings.gradle b/XixunPlayer/settings.gradle
new file mode 100644
index 0000000..f507d7b
--- /dev/null
+++ b/XixunPlayer/settings.gradle
@@ -0,0 +1,17 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "XixunPlayer"
+include ':app'