fix(ci): restore sanity checks
This commit is contained in:
@@ -39,14 +39,16 @@ internal class AssLatencyCalibrator(
|
||||
private val overlaySurface: SurfaceView,
|
||||
private val onCalibrated: (Int) -> Unit,
|
||||
private val onDone: () -> Unit,
|
||||
private val log: (String) -> Unit,
|
||||
private val log: (String) -> Unit
|
||||
) {
|
||||
private val lock = Any()
|
||||
private val finished = AtomicBoolean(false)
|
||||
private val applyFail = AtomicInteger(0)
|
||||
|
||||
@Volatile private var frameIntervalNs: Long = 0L
|
||||
|
||||
@Volatile private var converged = false
|
||||
|
||||
@Volatile private var stopped = false
|
||||
|
||||
private var videoFrames = 0
|
||||
@@ -77,7 +79,11 @@ internal class AssLatencyCalibrator(
|
||||
doAttach = true
|
||||
}
|
||||
}
|
||||
if (giveUp) finishIncomplete() else if (doAttach) attach(videoSurface, releaseTimeNs, SurfaceTxProbe.SOURCE_VIDEO)
|
||||
if (giveUp) {
|
||||
finishIncomplete()
|
||||
} else if (doAttach) {
|
||||
attach(videoSurface, releaseTimeNs, SurfaceTxProbe.SOURCE_VIDEO)
|
||||
}
|
||||
}
|
||||
|
||||
fun probeOverlay(releaseTimeNs: Long) {
|
||||
@@ -114,7 +120,7 @@ internal class AssLatencyCalibrator(
|
||||
surfaceCount: Int,
|
||||
fenceState: Int,
|
||||
source: Int,
|
||||
callbackNs: Long,
|
||||
callbackNs: Long
|
||||
) {
|
||||
if (surfaceCount <= 0 || fenceState != FENCE_OK || releaseNs <= 0) return
|
||||
val relMs = (releaseNs - tag) / 1_000_000.0
|
||||
|
||||
@@ -168,6 +168,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
|
||||
private var videoZoomScale: Float = 1.0f
|
||||
private var assHandler: AssHandler? = null
|
||||
private var assSubtitleView: AssSubtitleSurfaceView? = null
|
||||
|
||||
// Touched from the codec metadata listener, the GL-thread overlay hook, and the main-thread
|
||||
// media-item-transition/dispose paths — keep visibility across threads.
|
||||
@Volatile private var latencyCalibrator: AssLatencyCalibrator? = null
|
||||
@@ -739,7 +740,7 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener {
|
||||
overlaySurface = assView,
|
||||
onCalibrated = { frames -> onAssLatencyCalibrated(frames) },
|
||||
onDone = { assSubtitleView?.setPreSwapProbe(null) },
|
||||
log = { msg -> emitLog("info", "ass-latency-cal", msg) },
|
||||
log = { msg -> emitLog("info", "ass-latency-cal", msg) }
|
||||
).also {
|
||||
it.start()
|
||||
// Bind the hook to THIS instance, not the volatile field, so a concurrent transition
|
||||
|
||||
@@ -36,7 +36,7 @@ object SurfaceTxProbe {
|
||||
surfaceCount: Int,
|
||||
fenceState: Int,
|
||||
source: Int,
|
||||
callbackNs: Long,
|
||||
callbackNs: Long
|
||||
) {
|
||||
val s = sink ?: return
|
||||
s.invoke(tag, latchNs, releaseNs, surfaceCount, fenceState, source, callbackNs)
|
||||
|
||||
@@ -25,7 +25,7 @@ android {
|
||||
// A shared cache keeps per-ABI CMake runs from redownloading libass.
|
||||
arguments += listOf(
|
||||
"-DANDROID_STL=c++_shared",
|
||||
"-DLIBASS_CACHE_DIR=${layout.buildDirectory.get().asFile}/libass-prebuilt",
|
||||
"-DLIBASS_CACHE_DIR=${layout.buildDirectory.get().asFile}/libass-prebuilt"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,8 +298,8 @@ JNIEXPORT jobject JNICALL Java_com_edde746_plezy_libass_AssRender_nativeAssRende
|
||||
const jboolean hasOutput = imageListHasOutput(image) ? JNI_TRUE : JNI_FALSE;
|
||||
if (tAss - t0 > 40) {
|
||||
__android_log_print(
|
||||
ANDROID_LOG_WARN, LOG_TAG, "slow render t=%lldms: ass=%lldms (changed=%d, hasOutput=%d)",
|
||||
(long long)time, tAss - t0, changed, hasOutput == JNI_TRUE);
|
||||
ANDROID_LOG_WARN, LOG_TAG, "slow render t=%lldms: ass=%lldms (changed=%d, hasOutput=%d)", (long long)time,
|
||||
tAss - t0, changed, hasOutput == JNI_TRUE);
|
||||
}
|
||||
return (*env)->NewObject(env, atlasFrameClass, ctor, 0, 0, 0, changed, 0, hasOutput);
|
||||
}
|
||||
@@ -569,8 +569,7 @@ static EGLint gFtPresentName = EGL_DISPLAY_PRESENT_TIME_ANDROID;
|
||||
// Probes the extension on the currently-current draw surface and enables capture.
|
||||
// Re-resolves the display/surface each call so surface recreation is handled.
|
||||
// Returns one of the FT_* codes above.
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_edde746_plezy_libass_AssFrameTimestamps_nativeInit(JNIEnv* env, jclass clazz) {
|
||||
JNIEXPORT jint JNICALL Java_com_edde746_plezy_libass_AssFrameTimestamps_nativeInit(JNIEnv* env, jclass clazz) {
|
||||
gFtSurface = EGL_NO_SURFACE;
|
||||
EGLDisplay dpy = eglGetCurrentDisplay();
|
||||
EGLSurface surf = eglGetCurrentSurface(EGL_DRAW);
|
||||
@@ -632,8 +631,7 @@ Java_com_edde746_plezy_libass_AssFrameTimestamps_nativeGetNextFrameId(JNIEnv* en
|
||||
// Present (or composition) time for frameId (System.nanoTime() domain), or the
|
||||
// PENDING(-2)/INVALID(-1) sentinels. Reported a few frames after the swap.
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_edde746_plezy_libass_AssFrameTimestamps_nativeGetDisplayPresentTime(
|
||||
JNIEnv* env, jclass clazz, jlong frameId) {
|
||||
Java_com_edde746_plezy_libass_AssFrameTimestamps_nativeGetDisplayPresentTime(JNIEnv* env, jclass clazz, jlong frameId) {
|
||||
if (pEglGetFrameTimestamps == NULL || gFtSurface == EGL_NO_SURFACE) return EGL_TIMESTAMP_INVALID_ANDROID;
|
||||
const EGLint names[1] = {gFtPresentName};
|
||||
EGLnsecsANDROID values[1] = {0};
|
||||
|
||||
@@ -67,8 +67,7 @@ static int64_t readFenceTimeNs(int fd, int* outStatus) {
|
||||
if (ioctl(fd, SYNC_IOC_FILE_INFO, &probe) < 0) return -1;
|
||||
*outStatus = probe.status;
|
||||
if (probe.status != 1 || probe.num_fences == 0) return -1;
|
||||
struct stp_fence_info* arr =
|
||||
(struct stp_fence_info*)calloc(probe.num_fences, sizeof(struct stp_fence_info));
|
||||
struct stp_fence_info* arr = (struct stp_fence_info*)calloc(probe.num_fences, sizeof(struct stp_fence_info));
|
||||
if (!arr) return -1;
|
||||
struct stp_file_info req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
@@ -118,10 +117,7 @@ static int resolveSc(void) {
|
||||
p_getControls = (pf_getControls)dlsym(h, "ASurfaceTransactionStats_getASurfaceControls");
|
||||
p_prevRelease = (pf_prevRelease)dlsym(h, "ASurfaceTransactionStats_getPreviousReleaseFenceFd");
|
||||
p_releaseControls = (pf_releaseControls)dlsym(h, "ASurfaceTransactionStats_releaseASurfaceControls");
|
||||
gScOk = (p_fromJava && p_setOnComplete && p_latchTime && p_getControls && p_prevRelease &&
|
||||
p_releaseControls)
|
||||
? 1
|
||||
: 0;
|
||||
gScOk = (p_fromJava && p_setOnComplete && p_latchTime && p_getControls && p_prevRelease && p_releaseControls) ? 1 : 0;
|
||||
return gScOk;
|
||||
}
|
||||
|
||||
@@ -143,12 +139,12 @@ static int64_t nowMonoNs(void) {
|
||||
return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
||||
}
|
||||
|
||||
static void reportResult(JNIEnv* env, int64_t tag, int64_t latchNs, int64_t releaseNs, int count,
|
||||
int fenceState, int source, int64_t cbNs) {
|
||||
static void reportResult(
|
||||
JNIEnv* env, int64_t tag, int64_t latchNs, int64_t releaseNs, int count, int fenceState, int source, int64_t cbNs) {
|
||||
if (!env || !gProbeClass || !gOnResult) return;
|
||||
(*env)->CallStaticVoidMethod(env, gProbeClass, gOnResult, (jlong)tag, (jlong)latchNs,
|
||||
(jlong)releaseNs, (jint)count, (jint)fenceState, (jint)source,
|
||||
(jlong)cbNs);
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, gProbeClass, gOnResult, (jlong)tag, (jlong)latchNs, (jlong)releaseNs, (jint)count, (jint)fenceState,
|
||||
(jint)source, (jlong)cbNs);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
}
|
||||
|
||||
|
||||
+11
-10
@@ -213,10 +213,9 @@ internal class AssAtlasPipeline(
|
||||
// first actual render. Confined to the libass thread after creation.
|
||||
private var slots: AtlasSlots? = null
|
||||
|
||||
private fun rendererStateGeneration(): Long =
|
||||
assHandler.render?.let {
|
||||
(System.identityHashCode(it).toLong() shl 32) or (it.stateGeneration.toLong() and 0xffffffffL)
|
||||
} ?: -1L
|
||||
private fun rendererStateGeneration(): Long = assHandler.render?.let {
|
||||
(System.identityHashCode(it).toLong() shl 32) or (it.stateGeneration.toLong() and 0xffffffffL)
|
||||
} ?: -1L
|
||||
|
||||
private fun acquireSlots(): AtlasSlots {
|
||||
slots?.let { return it }
|
||||
@@ -484,8 +483,10 @@ private class SubtitleFramePhaseEstimator {
|
||||
|
||||
val ptsDeltaUs = ptsUs - prevPtsUs
|
||||
val releaseDeltaNs = releaseNs - prevReleaseNs
|
||||
if (ptsDeltaUs <= 0 || ptsDeltaUs > MAX_DELTA_US ||
|
||||
releaseDeltaNs <= 0 || releaseDeltaNs > MAX_DELTA_NS
|
||||
if (ptsDeltaUs <= 0 ||
|
||||
ptsDeltaUs > MAX_DELTA_US ||
|
||||
releaseDeltaNs <= 0 ||
|
||||
releaseDeltaNs > MAX_DELTA_NS
|
||||
) {
|
||||
deltaCount = 0
|
||||
deltaIndex = 0
|
||||
@@ -504,8 +505,7 @@ private class SubtitleFramePhaseEstimator {
|
||||
return copy[count / 2]
|
||||
}
|
||||
|
||||
private fun saturatedAdd(value: Long, delta: Long): Long =
|
||||
if (delta > 0 && value > Long.MAX_VALUE - delta) Long.MAX_VALUE else value + delta
|
||||
private fun saturatedAdd(value: Long, delta: Long): Long = if (delta > 0 && value > Long.MAX_VALUE - delta) Long.MAX_VALUE else value + delta
|
||||
|
||||
private companion object {
|
||||
const val UNSET = Long.MIN_VALUE
|
||||
@@ -947,12 +947,12 @@ private class AtlasGlThread(
|
||||
copy.sort()
|
||||
measuredIntervalNs = copy[releaseDeltaCount / 2]
|
||||
swapLeadNs = (measuredIntervalNs / 2).coerceIn(
|
||||
SCHEDULED_SWAP_LEAD_MIN_NS, SCHEDULED_SWAP_LEAD_MAX_NS
|
||||
SCHEDULED_SWAP_LEAD_MIN_NS,
|
||||
SCHEDULED_SWAP_LEAD_MAX_NS
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private lateinit var handler: Handler
|
||||
private var eglDisplay: EGLDisplay = EGL14.EGL_NO_DISPLAY
|
||||
private var eglContext: EGLContext = EGL14.EGL_NO_CONTEXT
|
||||
@@ -1322,6 +1322,7 @@ private class AtlasGlThread(
|
||||
private const val MSG_SIZE_CHANGED = 3
|
||||
private const val MSG_RELEASE = 4
|
||||
private const val SYNC_LOG_INTERVAL_SWAPS = 120L
|
||||
|
||||
// Missing SurfaceFlinger's latch deadline costs a full refresh, so keep the
|
||||
// margin proportional to the active cadence.
|
||||
private const val SCHEDULED_SWAP_LEAD_MIN_NS = 6_000_000L
|
||||
|
||||
+1
-2
@@ -210,8 +210,7 @@ internal class SpecRenderEngine(
|
||||
return SpecWrite(target, frame)
|
||||
}
|
||||
|
||||
private fun isImplicitBlank(frame: AssAtlasFrame): Boolean =
|
||||
!frame.hasOutput && libassLastFrame?.hasOutput == true
|
||||
private fun isImplicitBlank(frame: AssAtlasFrame): Boolean = !frame.hasOutput && libassLastFrame?.hasOutput == true
|
||||
|
||||
/**
|
||||
* Pre-renders [ptsUs] (an upcoming event's start) purely to warm the
|
||||
|
||||
@@ -7,10 +7,19 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/mpv/mpv.dart';
|
||||
import 'package:plezy/mpv/player/platform/player_android.dart';
|
||||
import 'package:plezy/mpv/player/player_native.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
|
||||
import '../test_helpers/prefs.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUp(() async {
|
||||
resetSharedPreferencesForTest();
|
||||
SettingsService.resetForTesting();
|
||||
await SettingsService.getInstance();
|
||||
});
|
||||
|
||||
group('player open', () {
|
||||
test('ExoPlayer clears stale Dart track state before opening new media', () async {
|
||||
await _withMockChannels(
|
||||
|
||||
@@ -3,6 +3,9 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/mpv/player/platform/player_android.dart';
|
||||
import 'package:plezy/mpv/player/player_base.dart';
|
||||
import 'package:plezy/mpv/player/player_native.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
|
||||
import '../test_helpers/prefs.dart';
|
||||
|
||||
/// Guards the channel contract: every property [PlayerBase.handlePropertyChange]
|
||||
/// depends on for core state must be registered by each backend at init.
|
||||
@@ -12,6 +15,12 @@ import 'package:plezy/mpv/player/player_native.dart';
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUp(() async {
|
||||
resetSharedPreferencesForTest();
|
||||
SettingsService.resetForTesting();
|
||||
await SettingsService.getInstance();
|
||||
});
|
||||
|
||||
const coreNames = {
|
||||
'time-pos',
|
||||
'duration',
|
||||
|
||||
Reference in New Issue
Block a user