mirror of
https://github.com/meowarex/rl-mobile.git
synced 2026-08-26 22:17:44 +10:00
265 lines
10 KiB
Java
265 lines
10 KiB
Java
package radiant;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.drawable.BitmapDrawable;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.SystemClock;
|
|
import android.view.Choreographer;
|
|
|
|
import androidx.compose.foundation.layout.SizeKt;
|
|
import androidx.compose.foundation.layout.SpacerKt;
|
|
import androidx.compose.runtime.Composer;
|
|
import androidx.compose.runtime.MutableIntState;
|
|
import androidx.compose.runtime.SnapshotIntStateKt;
|
|
import androidx.compose.runtime.MutableState;
|
|
import androidx.compose.runtime.SnapshotStateKt;
|
|
import androidx.compose.ui.Modifier;
|
|
import androidx.compose.ui.draw.DrawModifierKt;
|
|
import androidx.compose.ui.geometry.Size;
|
|
import androidx.compose.ui.graphics.AndroidCanvas_androidKt;
|
|
import androidx.compose.ui.graphics.drawscope.DrawScope;
|
|
import androidx.compose.ui.platform.AndroidCompositionLocals_androidKt;
|
|
|
|
import coil.request.CachePolicy;
|
|
|
|
import com.tidal.android.feature.playerscreen.ui.composables.p3;
|
|
import com.tidal.android.feature.playerscreen.ui.model.PlayerBackgroundStyle;
|
|
import com.tidal.android.image.coil.base.CoilImageLoader;
|
|
import com.tidal.android.image.core.b;
|
|
|
|
import dev.kawarp.KawarpEngine;
|
|
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
|
|
/**
|
|
* MARKER: Kawarp Backdrop - TIDAL glue over dev.kawarp.KawarpEngine (Kawarp-AGSL library)
|
|
*
|
|
* The engine (shader, blur pipeline, settings, crossfade) lives in the Kawarp-AGSL library
|
|
* (cloned at tools/kawarp-agsl) and is compiled into this bundle by tools/kawarp-build/build.sh;
|
|
* this class only holds what is TIDAL/Compose-specific
|
|
*/
|
|
public final class Kawarp implements am0.l, Runnable, Choreographer.FrameCallback, b0.c {
|
|
|
|
/** Stop the frame loop once nothing has drawn for this long (player left / backgrounded). */
|
|
private static final long IDLE_STOP_MS = 500L;
|
|
|
|
/**
|
|
* Patch option plumbing. The 0x5241xxxx literals are sentinels that build.sh rewrites into
|
|
* __RL_*__ placeholders for the Manager to substitute; routing them through tk() stops javac
|
|
* constant-folding them out of existence.
|
|
*/
|
|
private static int tk(int v) { return v; }
|
|
|
|
private static final ExecutorService LOADER = Executors.newSingleThreadExecutor();
|
|
|
|
/**
|
|
* Bumped every frame so the draw lambda's snapshot read invalidates the draw phase.
|
|
* An int state rather than MutableState<Integer>: this ticks at 60 Hz, and boxing here
|
|
* would allocate an Integer per frame for nothing.
|
|
*/
|
|
private static final MutableIntState frameState = SnapshotIntStateKt.mutableIntStateOf(0);
|
|
/** Flips true on any shader failure; read during composition so we recompose onto p3.a. */
|
|
private static final MutableState fallbackState =
|
|
SnapshotStateKt.mutableStateOf$default(Boolean.FALSE, null, 2, null);
|
|
|
|
/** The shared instance render passes to drawBehind (also serves as the frame callback). */
|
|
private static final Kawarp DRAW = new Kawarp(0, null, 0);
|
|
|
|
private static KawarpEngine engine;
|
|
/** TIDAL's application-scoped Coil loader and the context its requests are built with. */
|
|
private static volatile coil.f loader;
|
|
private static volatile Context appContext;
|
|
private static volatile String requestedUuid;
|
|
private static volatile int loadToken;
|
|
|
|
private static long lastDrawUptime;
|
|
private static boolean frameScheduled;
|
|
private static int frameTick;
|
|
|
|
/** Set when this instance is a queued cover load; 0/null/0 on the shared DRAW instance. */
|
|
private final int albumId;
|
|
private final String uuid;
|
|
private final int token;
|
|
|
|
private Kawarp(int albumId, String uuid, int token) {
|
|
this.albumId = albumId;
|
|
this.uuid = uuid;
|
|
this.token = token;
|
|
}
|
|
|
|
|
|
// Composition
|
|
|
|
|
|
/**
|
|
* Drop-in replacement for TIDAL's PlayerBackground composable (p3.b) - the patch redirects
|
|
* the call site here so the whole backdrop works <3
|
|
*/
|
|
public static void b(PlayerBackgroundStyle style, long color, int albumId, String coverUuid,
|
|
boolean isPlaying, boolean isSeeking, am0.a progress, Modifier modifier,
|
|
Composer composer, int changed) {
|
|
render(albumId, coverUuid, isPlaying, modifier, composer);
|
|
}
|
|
|
|
/** Shared by the player screen and by Cover Everywhere (radiant/HomeBackdrop). */
|
|
public static void render(int albumId, String coverUuid, boolean isPlaying,
|
|
Modifier modifier, Composer composer) {
|
|
composer.startReplaceGroup(0x52415750);
|
|
attach(composer);
|
|
boolean ok = !((Boolean) fallbackState.getValue()).booleanValue() && ensureEngine();
|
|
if (!ok) {
|
|
// No (working) AGSL here - fall back to TIDAL's own blurred backdrop.
|
|
p3.a(albumId, coverUuid, isPlaying, false, null, modifier, composer, 0);
|
|
} else {
|
|
engine.setPlaying(isPlaying);
|
|
request(albumId, coverUuid);
|
|
Modifier m = DrawModifierKt.drawBehind(SizeKt.fillMaxSize(modifier, 1.0f), DRAW);
|
|
SpacerKt.Spacer(m, composer, 0);
|
|
}
|
|
composer.endReplaceGroup();
|
|
}
|
|
|
|
/**
|
|
* Grab the app's image loader the same way TIDAL's composables do (yd0.c)
|
|
*/
|
|
private static void attach(Composer composer) {
|
|
if (loader != null) return;
|
|
try {
|
|
Object local = composer.consume(AndroidCompositionLocals_androidKt.getLocalContext());
|
|
if (!(local instanceof Context)) return;
|
|
Context app = ((Context) local).getApplicationContext();
|
|
if (!(app instanceof ce0.b.a)) return;
|
|
Object tidalLoader = ((ce0.b.a) app).a().a();
|
|
if (!(tidalLoader instanceof CoilImageLoader)) return;
|
|
appContext = app;
|
|
loader = ((CoilImageLoader) tidalLoader).a;
|
|
} catch (Throwable t) {
|
|
// No loader -> request() never runs and the backdrop simply stays on the last cover.
|
|
}
|
|
}
|
|
|
|
/** Engine construction compiles the AGSL so this doubles as the support probe. */
|
|
private static boolean ensureEngine() {
|
|
if (engine != null) return true;
|
|
if (!KawarpEngine.isSupported()) return false;
|
|
try {
|
|
KawarpEngine e = new KawarpEngine();
|
|
e.setWarpIntensity(Float.intBitsToFloat(tk(0x52410001)));
|
|
e.setAnimationSpeed(Float.intBitsToFloat(tk(0x52410002)));
|
|
e.setScale(Float.intBitsToFloat(tk(0x52410003)));
|
|
e.setDithering(Float.intBitsToFloat(tk(0x52410004)));
|
|
e.setAutoDarken(Float.intBitsToFloat(tk(0x52410005)));
|
|
e.setPlaybackReactive(tk(0x52410006) != 0);
|
|
e.setBlurPasses(tk(0x52410007));
|
|
e.setContrast(Float.intBitsToFloat(tk(0x52410008)));
|
|
e.setSaturation(Float.intBitsToFloat(tk(0x52410009)));
|
|
e.setBrightness(Float.intBitsToFloat(tk(0x5241000a)));
|
|
engine = e;
|
|
return true;
|
|
} catch (Throwable t) {
|
|
fallbackState.setValue(Boolean.TRUE); // AGSL rejected: stay on the native backdrop
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
// Cover loading: a queued Kawarp is both the LOADER task and the request's Coil Target
|
|
|
|
|
|
private static void request(int albumId, String coverUuid) {
|
|
if (coverUuid == null || coverUuid.length() == 0 || coverUuid.equals(requestedUuid)) return;
|
|
if (loader == null) return; // attach() failed; retried next composition
|
|
requestedUuid = coverUuid;
|
|
LOADER.execute(new Kawarp(albumId, coverUuid, ++loadToken));
|
|
}
|
|
|
|
/**
|
|
* TIDAL's own backdrop request (PlayerBackgroundKt.rememberBlurredArtwork)
|
|
*/
|
|
public void run() {
|
|
try {
|
|
xd0.g tidal = new xd0.g(appContext, new b.a(String.valueOf(albumId), uuid),
|
|
null, null, false, null, null, false);
|
|
coil.request.h.a req = coil.request.h.a(com.tidal.android.image.coil.c.a(tidal, null));
|
|
coil.request.b d = req.b;
|
|
req.b = new coil.request.b(d.a, d.b, d.c, d.d, d.e, d.f, d.g, d.h,
|
|
d.i, d.j, CachePolicy.DISABLED);
|
|
req.d = this;
|
|
loader.b(req.a());
|
|
} catch (Throwable t) {
|
|
fail();
|
|
}
|
|
}
|
|
|
|
/** Target.onSuccess */
|
|
public void a(Drawable drawable) {
|
|
if (token != loadToken) return; // user skipped on
|
|
Bitmap cover = toBitmap(drawable);
|
|
if (cover == null) { fail(); return; }
|
|
// setCover copies, the bitmap stays Coil's (memory cache)
|
|
engine.setCover(cover);
|
|
}
|
|
|
|
/** Target.onStart - nothing to show while loading. */
|
|
public void b(Drawable placeholder) {}
|
|
|
|
/** Target.onError - not in any of TIDAL's stores. */
|
|
public void d(Drawable error) { fail(); }
|
|
|
|
private void fail() {
|
|
if (token == loadToken) requestedUuid = null;
|
|
}
|
|
|
|
private static Bitmap toBitmap(Drawable d) {
|
|
if (d == null) return null;
|
|
if (d instanceof BitmapDrawable) return ((BitmapDrawable) d).getBitmap();
|
|
Bitmap out = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888);
|
|
d.setBounds(0, 0, 256, 256);
|
|
d.draw(new Canvas(out));
|
|
return out;
|
|
}
|
|
|
|
|
|
// Frame loop
|
|
|
|
|
|
public void doFrame(long frameTimeNanos) {
|
|
frameScheduled = false;
|
|
// Nothing has drawn for a while: the backdrop is off-screen, so stop burning frames.
|
|
if (SystemClock.uptimeMillis() - lastDrawUptime > IDLE_STOP_MS) return;
|
|
frameState.setIntValue(++frameTick);
|
|
schedule();
|
|
}
|
|
|
|
private static void schedule() {
|
|
if (frameScheduled) return;
|
|
frameScheduled = true;
|
|
Choreographer.getInstance().postFrameCallback(DRAW);
|
|
}
|
|
|
|
public Object invoke(Object scope) {
|
|
draw((DrawScope) scope);
|
|
return kotlin.u.a;
|
|
}
|
|
|
|
private static void draw(DrawScope scope) {
|
|
// Snapshot read: this is what ties the draw phase to the frame clock.
|
|
frameState.getIntValue();
|
|
lastDrawUptime = SystemClock.uptimeMillis();
|
|
schedule();
|
|
|
|
long packed = scope.getSizeNHjbRc();
|
|
float width = Size.getWidthImpl(packed);
|
|
float height = Size.getHeightImpl(packed);
|
|
try {
|
|
engine.draw(AndroidCanvas_androidKt.getNativeCanvas(scope.getDrawContext().getCanvas()),
|
|
width, height);
|
|
} catch (Throwable t) {
|
|
fallbackState.setValue(Boolean.TRUE); // AGSL draw failed: recompose onto p3.a
|
|
}
|
|
}
|
|
}
|