This commit is contained in:
Gangphon 2025-09-19 19:13:20 +08:00
parent a20ef88c19
commit 4a3e4360bd
5 changed files with 119 additions and 19 deletions

View File

@ -11,6 +11,8 @@ import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@ -19,7 +21,6 @@ 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;
@ -31,6 +32,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
@ -52,7 +54,6 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac
long syncMs;
int state;
@RequiresApi(api = Build.VERSION_CODES.R)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -63,6 +64,8 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac
setContentView(backView = new BackView(MainActivity.this));
state = 5;
hookWebView();
if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) init();
else {
Util.println("---- No permission, Try again ...");
@ -86,7 +89,40 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac
Util.makeText(this, "Request Permission Failed").show();
}
}
@SuppressLint("SoonBlockedPrivateApi")
public static void hookWebView() {
if(android.os.Process.myUid() != android.os.Process.SYSTEM_UID) return;
try {
var factoryClass = Class.forName("android.webkit.WebViewFactory");
var field = factoryClass.getDeclaredField("sProviderInstance");
field.setAccessible(true);
var sProviderInstance = field.get(null);
if (sProviderInstance != null) {
Util.println("sProviderInstance isn't null");
return;
}
Method getProviderClassMethod;
if (Build.VERSION.SDK_INT > 22) { // above 22
getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
} else if (Build.VERSION.SDK_INT == 22) { // method name is a little different
getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
} else { // no security check below 22
Util.println("Don't need to Hook WebView");
return;
}
getProviderClassMethod.setAccessible(true);
var providerClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);
var delegateClass = Class.forName("android.webkit.WebViewDelegate");
var declaredConstructor = delegateClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
sProviderInstance = providerClass.getDeclaredMethod("create", delegateClass).invoke(providerClass, declaredConstructor.newInstance());
field.set("sProviderInstance", sProviderInstance);
Util.println("sProviderInstance"+sProviderInstance.toString());
Util.println("Hook Done!");
} catch (Throwable e) {
Util.printStackTrace(e);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
@ -103,6 +139,7 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac
ins = null;
for(var rece : reces) unregisterReceiver(rece);
for(var service : services) unbindService(service);
if(audioManager!=null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) audioManager.abandonAudioFocusRequest(audioFocusRequest);
try (var fout = new FileOutputStream(Util.programDir+"/cfg")){
Util.cfg.write(fout);
fout.flush();
@ -324,8 +361,16 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac
}
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);
var page = avas.get(curAva);
for(var layer : page.layers) for(var src : layer.srcs) if(src.isShow) {
showHides.add(new MainActivity.ShowHide(ms+1000, src, 'H'));
src.isShow = false;
}
if(code > 0 && avas.get(curAva)==progView.pages.get(code-1)) {
showHides.add(new ShowHide(ms + 1000, ()->{
for(var call : progView.calls) call.doFrame(ms);
}));
}
}
if(code==0) syncProg(ms, 0);
else {
@ -385,8 +430,52 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac
}
}).start();
}
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK).setOnAudioFocusChangeListener((int focusChange)-> {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
Util.println("AUDIOFOCUS_GAIN");
Util.isAudioGain = true;
break;
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
Util.println("AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK");
Util.isAudioGain = true;
break;
case AudioManager.AUDIOFOCUS_LOSS:
Util.println("AUDIOFOCUS_LOSS");
Util.isAudioGain = false;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
Util.println("AUDIOFOCUS_LOSS_TRANSIENT"); //短暂失去音频焦点暂停播放等待又一次获得音频焦点
Util.isAudioGain = false;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
Util.println("AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK"); //减少声音就可以
Util.isAudioGain = false;
break;
}
View view;
if(Util.isAudioGain) {
if(insView!=null) {
for(int cc=0; cc<insView.getChildCount(); cc++) if((view = insView.getChildAt(cc)) instanceof SrcVideo) ((SrcVideo) view).ijkPlayer.setVolume(((SrcVideo) view).vol, ((SrcVideo) view).vol);
} else if(progView!=null) {
for(int cc=0; cc<progView.getChildCount(); cc++) if((view = progView.getChildAt(cc)) instanceof SrcVideo) ((SrcVideo) view).ijkPlayer.setVolume(((SrcVideo) view).vol, ((SrcVideo) view).vol);
}
} else {
if(insView!=null) {
for(int cc=0; cc<insView.getChildCount(); cc++) if((view = insView.getChildAt(cc)) instanceof SrcVideo) ((SrcVideo) view).ijkPlayer.setVolume(0,0);
} else if(progView!=null) {
for(int cc=0; cc<progView.getChildCount(); cc++) if((view = progView.getChildAt(cc)) instanceof SrcVideo) ((SrcVideo) view).ijkPlayer.setVolume(0,0);
}
}
}).build();
Util.isAudioGain = true;
Util.println("requestAudioFocus: "+audioManager.requestAudioFocus(audioFocusRequest));
}
}
AudioManager audioManager;
AudioFocusRequest audioFocusRequest;
public void stopProg() {
avas.clear();
curAva = 0;
@ -520,10 +609,17 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac
}
public void afterCheck(Prog view, byte[] json) {
try {
var ms = (System.currentTimeMillis()+999)/1000*1000;
if(view.isInsert) {
if(insView!=null) insView.release();
insView = view;
try { avas.get(curAva).hide();} catch (Throwable ignored) {}
try {
var page = avas.get(curAva);
for(var layer : page.layers) for(var src : layer.srcs) if(src.isShow) {
showHides.add(new ShowHide(ms+1000, src, 'H'));
src.isShow = false;
}
} catch (Throwable ignored) {}
setContentView(insView);
} else {
if(progView!=null) progView.release();
@ -538,7 +634,7 @@ public class MainActivity extends Activity implements Choreographer.FrameCallbac
state = 5;
System.gc();
Util.println("Init Sync");
syncProg((System.currentTimeMillis()+999)/1000*1000, 0);
syncProg(ms, 0);
if(canAdd) {
choreographer.postFrameCallback(this);
canAdd = false;

View File

@ -374,8 +374,8 @@ public class Prog extends AbsLayout {
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.programDir + "/" + id, source);
else if(src.type.equals("WebURL")) {
var url = source.str("url");
if(url!=null) src.view = new SrcWeb(this, url, source);
var url = source.stnn("url").trim();
if(! url.isEmpty()) src.view = new SrcWeb(this, url, 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);
@ -568,8 +568,8 @@ public class Prog extends AbsLayout {
}
void release() {
if(view instanceof SrcVideo) {
((SrcVideo)view).release();
box.removeView(view);
((SrcVideo)view).release();
view = null;
}
}
@ -747,10 +747,6 @@ public class Prog extends AbsLayout {
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, List<MainActivity.ShowHide> shows) {
endMilli = start + sDur;
for(var layer : layers) {

View File

@ -16,10 +16,12 @@ public class SrcVideo extends TextureView implements TextureView.SurfaceTextureL
IjkMediaPlayer ijkPlayer;
Surface surface;
float vol;
long bitRate;
public SrcVideo(Context context, String path, float vol, int dur, long seek, boolean useSW) {
super(context);
this.vol = vol;
setSurfaceTextureListener(this);
Util.println(" video new");
ijkPlayer = new IjkMediaPlayer();
@ -33,7 +35,8 @@ public class SrcVideo extends TextureView implements TextureView.SurfaceTextureL
try {
ijkPlayer.setDataSource(path);
ijkPlayer.setLooping(true);
ijkPlayer.setVolume(vol, vol);
if(Util.isAudioGain) ijkPlayer.setVolume(vol, vol);
else ijkPlayer.setVolume(0, 0);
ijkPlayer.setOnPreparedListener((IMediaPlayer var1)->{
ijkPlayer.setOnPreparedListener(null);
if(getAlpha() < 0.25) {
@ -74,15 +77,17 @@ public class SrcVideo extends TextureView implements TextureView.SurfaceTextureL
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {}
void release() {
Util.println(" video release");
Util.println(" video releasing");
if(ijkPlayer!=null) {
ijkPlayer.release();
ijkPlayer = null;
}
Util.println(" surface releasing");
if(surface!=null) {
surface.release();
surface = null;
}
Util.println(" surface released");
}
// @Override

View File

@ -52,7 +52,10 @@ public class SrcWeb extends WebView implements Choreographer.FrameCallback {
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if(request.isForMainFrame() && error.getErrorCode()==-2 && request.getUrl().toString().equals(url)) needReload = true;
if(request.isForMainFrame() && error.getErrorCode()==-2 && request.getUrl().toString().equals(url)) {
needReload = true;
Util.println(" WebView Need Reload");
}
Util.println(" WebView ReceivedError "+error.getErrorCode()+" "+error.getDescription()+"; isForMainFrame "+request.isForMainFrame()+" URL "+request.getUrl());
}
}

View File

@ -43,7 +43,7 @@ public class Util {
public static volatile long downId;
public static int screenWidth = 1920, screenHeight = 1080;
public static double lat, lng;
public static boolean isScreenOn, logOn;
public static boolean isScreenOn, isAudioGain, logOn;
public static void initDir(Context ctx) {
var dir = Build.VERSION.SDK_INT < Build.VERSION_CODES.R ? Environment.getExternalStorageDirectory().getAbsolutePath() + "/XixunPlayer" : ctx.getExternalFilesDir(null).getAbsolutePath();