Compare commits

...

2 Commits

Author SHA1 Message Date
My-Responsitories
fb9568a628 opt: remove volatile (#2255) 2026-05-31 21:11:24 +08:00
dom
90590faaaf refa pip
Signed-off-by: dom <githubaccount56556@proton.me>
2026-05-31 19:01:02 +08:00
11 changed files with 154 additions and 118 deletions

View File

@@ -1,5 +1,6 @@
package com.example.piliplus;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.SearchManager;
@@ -18,23 +19,29 @@ import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.provider.Settings;
import android.util.Rational;
import android.view.WindowManager;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import com.github.dart_lang.jni_flutter.JniFlutterPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@Keep
public final class AndroidHelper {
public static volatile boolean isFoldable = false;
public static final boolean isFoldable;
public static final boolean isPipAvailable;
public static volatile boolean isPipMode = false;
public static volatile boolean isPipAvailable = false;
static {
PackageManager pm = getContext().getPackageManager();
isFoldable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm.hasSystemFeature(PackageManager.FEATURE_SENSOR_HINGE_ANGLE);
isPipAvailable = pm.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
}
private AndroidHelper() {
}
@@ -55,8 +62,8 @@ public final class AndroidHelper {
}
public static void biliSendCommAntifraud(
int action, long oid, int type, long rpId, long root, long parent, long ctime, @NotNull String commentText,
String pictures, @NotNull String sourceId, long uid, @NotNull String cookie
int action, long oid, int type, long rpId, long root, long parent, long ctime, @NonNull String commentText,
String pictures, @NonNull String sourceId, long uid, @NonNull String cookie
) {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -106,7 +113,7 @@ public final class AndroidHelper {
}
}
public static boolean openMusic(@NotNull String title, String artist, String album) {
public static boolean openMusic(@NonNull String title, String artist, String album) {
Intent intent = new Intent(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(SearchManager.QUERY, title);
@@ -142,12 +149,31 @@ public final class AndroidHelper {
return false;
}
public static void setPipAutoEnterEnabled(boolean autoEnable, long engineId) {
public static void enterPip(int width, int height, boolean autoEnter, long engineId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Activity activity = JniFlutterPlugin.getActivity(engineId);
assert activity != null;
PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(width, height));
if (autoEnter) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
builder.setAutoEnterEnabled(true);
activity.setPictureInPictureParams(builder.build());
}
} else {
activity.enterPictureInPictureMode(builder.build());
}
}
}
public static void disableAutoEnterPip(long engineId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PictureInPictureParams params = new PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnable).build();
android.app.Activity activity = JniFlutterPlugin.getActivity(engineId);
Activity activity = JniFlutterPlugin.getActivity(engineId);
if (activity != null) {
activity.setPictureInPictureParams(params);
activity.setPictureInPictureParams(new PictureInPictureParams.Builder()
.setAutoEnterEnabled(false)
.build()
);
}
}
}
@@ -170,7 +196,7 @@ public final class AndroidHelper {
}
}
public static void createShortcut(@NotNull String id, @NotNull String uri, @NotNull String label, @NotNull String icon) {
public static void createShortcut(@NonNull String id, @NonNull String uri, @NonNull String label, @NonNull String icon) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Context context = getContext();
ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
@@ -194,7 +220,7 @@ public final class AndroidHelper {
@Keep
public static final class ToDart {
public static volatile Runnable onUserLeaveHint;
public static volatile Runnable onConfigurationChanged;
public static Runnable onConfigurationChanged;
private ToDart() {
}

View File

@@ -1,7 +1,6 @@
package com.example.piliplus
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
@@ -51,17 +50,6 @@ class MainActivity : AudioServiceActivity() {
window.attributes.layoutInDisplayCutoutMode =
LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
AndroidHelper.isPipAvailable =
packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
AndroidHelper.isFoldable =
packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_HINGE_ANGLE)
} catch (_: Exception) {
}
}
}
override fun onDestroy() {
@@ -74,9 +62,7 @@ class MainActivity : AudioServiceActivity() {
AndroidHelper.ToDart.onUserLeaveHint?.run()
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean, newConfig: Configuration?
) {
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
AndroidHelper.isPipMode = isInPictureInPictureMode
}

View File

@@ -9,7 +9,6 @@ import 'package:PiliPlus/services/shutdown_timer_service.dart'
show shutdownTimerService;
import 'package:PiliPlus/utils/android/bindings.g.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:floating/floating.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
@@ -160,7 +159,7 @@ class _LiveHeaderControlState extends State<LiveHeaderControl>
ComBtn(
height: 30,
tooltip: '画中画',
onTap: () {
onTap: () {
if (PlatformUtils.isDesktop) {
plPlayerController.toggleDesktopPip();
return;

View File

@@ -275,7 +275,7 @@ class PlPlayerController with BlockConfigMixin {
}
}
late bool _shouldSetPip = false;
late bool _isAutoEnterPip = false;
static bool get _isCurrVideoPage {
final routing = Get.routing;
@@ -289,12 +289,11 @@ class PlPlayerController with BlockConfigMixin {
return routeName == '/videoV' || routeName == '/liveRoom';
}
void enterPip({bool isAuto = false}) {
void enterPip({bool autoEnter = false}) {
if (videoPlayerController != null) {
controls = false;
final state = videoPlayerController!.state;
PageUtils.enterPip(
isAuto: isAuto,
autoEnter: autoEnter,
width: state.width == 0 ? width : state.width,
height: state.height == 0 ? height : state.height,
);
@@ -302,8 +301,8 @@ class PlPlayerController with BlockConfigMixin {
}
void _disableAutoEnterPip() {
if (_shouldSetPip) {
PiliAndroidHelper.setPipAutoEnterEnabled(false);
if (_isAutoEnterPip) {
PiliAndroidHelper.disableAutoEnterPip();
}
}
@@ -578,12 +577,12 @@ class PlPlayerController with BlockConfigMixin {
}
if (Platform.isAndroid && autoPiP) {
if (DeviceUtils.sdkInt < 36) {
if (DeviceUtils.sdkInt < 31) {
AndroidHelper$ToDart.onUserLeaveHint = Runnable.implement(
$Runnable(run: _onUserLeaveHint),
);
} else {
_shouldSetPip = true;
_isAutoEnterPip = true;
}
}
}
@@ -942,9 +941,9 @@ class PlPlayerController with BlockConfigMixin {
stream.playing.listen((event) {
WakelockPlus.toggle(enable: event);
if (event) {
if (_shouldSetPip) {
if (_isAutoEnterPip) {
if (_isCurrVideoPage) {
enterPip(isAuto: true);
enterPip(autoEnter: true);
} else {
_disableAutoEnterPip();
}

View File

@@ -52,6 +52,7 @@ import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/forward_seek.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/mpv_convert_webp.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/play_pause_btn.dart';
import 'package:PiliPlus/utils/android/bindings.g.dart';
import 'package:PiliPlus/utils/connectivity_utils.dart';
import 'package:PiliPlus/utils/duration_utils.dart';
import 'package:PiliPlus/utils/extension/num_ext.dart';
@@ -967,6 +968,14 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
colorScheme = ColorScheme.of(context);
}
@override
void didUpdateWidget(covariant PLVideoPlayer oldWidget) {
super.didUpdateWidget(oldWidget);
if (Platform.isAndroid && AndroidHelper.isPipMode) {
plPlayerController.controls = false;
}
}
void _onPanStart(ScaleStartDetails details) {
_gestureType = null;
_initialFocalPoint = details.localFocalPoint;

View File

@@ -73,12 +73,18 @@ abstract final class PiliAndroidHelper {
}
@pragma('vm:prefer-inline')
static void setPipAutoEnterEnabled(bool autoEnable) =>
AndroidHelper.setPipAutoEnterEnabled(
autoEnable,
static void enterPip(int width, int height, bool autoEnter) =>
AndroidHelper.enterPip(
width,
height,
autoEnter,
PlatformDispatcher.instance.engineId!,
);
@pragma('vm:prefer-inline')
static void disableAutoEnterPip() =>
AndroidHelper.disableAutoEnterPip(PlatformDispatcher.instance.engineId!);
static (int, int)? maxScreenSize() {
final jIArr = AndroidHelper.maxScreenSize();
if (jIArr != null) {

View File

@@ -197,13 +197,19 @@ extension type AndroidHelper._(jni$_.JObject _$this) implements jni$_.JObject {
r'Z',
);
/// from: `static public volatile boolean isFoldable`
/// from: `static public final boolean isFoldable`
static core$_.bool get isFoldable =>
_id_isFoldable.getNullable(_class, jni$_.jboolean.type) as core$_.bool;
/// from: `static public volatile boolean isFoldable`
static set isFoldable(core$_.bool value) =>
_id_isFoldable.set(_class, jni$_.jboolean.type, value);
static final _id_isPipAvailable = _class.staticFieldId(
r'isPipAvailable',
r'Z',
);
/// from: `static public final boolean isPipAvailable`
static core$_.bool get isPipAvailable =>
_id_isPipAvailable.getNullable(_class, jni$_.jboolean.type)
as core$_.bool;
static final _id_isPipMode = _class.staticFieldId(
r'isPipMode',
@@ -218,20 +224,6 @@ extension type AndroidHelper._(jni$_.JObject _$this) implements jni$_.JObject {
static set isPipMode(core$_.bool value) =>
_id_isPipMode.set(_class, jni$_.jboolean.type, value);
static final _id_isPipAvailable = _class.staticFieldId(
r'isPipAvailable',
r'Z',
);
/// from: `static public volatile boolean isPipAvailable`
static core$_.bool get isPipAvailable =>
_id_isPipAvailable.getNullable(_class, jni$_.jboolean.type)
as core$_.bool;
/// from: `static public volatile boolean isPipAvailable`
static set isPipAvailable(core$_.bool value) =>
_id_isPipAvailable.set(_class, jni$_.jboolean.type, value);
static final _id_sdkInt = _class.staticMethodId(
r'sdkInt',
r'()I',
@@ -453,18 +445,20 @@ extension type AndroidHelper._(jni$_.JObject _$this) implements jni$_.JObject {
).boolean;
}
static final _id_setPipAutoEnterEnabled = _class.staticMethodId(
r'setPipAutoEnterEnabled',
r'(ZJ)V',
static final _id_enterPip = _class.staticMethodId(
r'enterPip',
r'(IIZJ)V',
);
static final _setPipAutoEnterEnabled =
static final _enterPip =
jni$_.ProtectedJniExtensions.lookup<
jni$_.NativeFunction<
jni$_.JThrowablePtr Function(
jni$_.Pointer<jni$_.Void>,
jni$_.JMethodIDPtr,
jni$_.VarArgs<(jni$_.Int32, jni$_.Int64)>,
jni$_.VarArgs<
(jni$_.Int32, jni$_.Int32, jni$_.Int32, jni$_.Int64)
>,
)
>
>('globalEnv_CallStaticVoidMethod')
@@ -474,19 +468,60 @@ extension type AndroidHelper._(jni$_.JObject _$this) implements jni$_.JObject {
jni$_.JMethodIDPtr,
core$_.int,
core$_.int,
core$_.int,
core$_.int,
)
>();
/// from: `static public void setPipAutoEnterEnabled(boolean autoEnable, long engineId)`
static void setPipAutoEnterEnabled(
core$_.bool autoEnable,
/// from: `static public void enterPip(int width, int height, boolean autoEnter, long engineId)`
static void enterPip(
core$_.int width,
core$_.int height,
core$_.bool autoEnter,
core$_.int engineId,
) {
final _$$classRef = _class.reference;
_setPipAutoEnterEnabled(
_enterPip(
_$$classRef.pointer,
_id_setPipAutoEnterEnabled.pointer,
autoEnable ? 1 : 0,
_id_enterPip.pointer,
width,
height,
autoEnter ? 1 : 0,
engineId,
).check();
}
static final _id_disableAutoEnterPip = _class.staticMethodId(
r'disableAutoEnterPip',
r'(J)V',
);
static final _disableAutoEnterPip =
jni$_.ProtectedJniExtensions.lookup<
jni$_.NativeFunction<
jni$_.JThrowablePtr Function(
jni$_.Pointer<jni$_.Void>,
jni$_.JMethodIDPtr,
jni$_.VarArgs<(jni$_.Int64,)>,
)
>
>('globalEnv_CallStaticVoidMethod')
.asFunction<
jni$_.JThrowablePtr Function(
jni$_.Pointer<jni$_.Void>,
jni$_.JMethodIDPtr,
core$_.int,
)
>();
/// from: `static public void disableAutoEnterPip(long engineId)`
static void disableAutoEnterPip(
core$_.int engineId,
) {
final _$$classRef = _class.reference;
_disableAutoEnterPip(
_$$classRef.pointer,
_id_disableAutoEnterPip.pointer,
engineId,
).check();
}
@@ -617,13 +652,13 @@ extension type AndroidHelper$ToDart._(jni$_.JObject _$this)
r'Ljava/lang/Runnable;',
);
/// from: `static public volatile java.lang.Runnable onConfigurationChanged`
/// from: `static public java.lang.Runnable onConfigurationChanged`
/// The returned object must be released after use, by calling the [release] method.
static Runnable? get onConfigurationChanged =>
_id_onConfigurationChanged.getNullable(_class, Runnable.type)
as Runnable?;
/// from: `static public volatile java.lang.Runnable onConfigurationChanged`
/// from: `static public java.lang.Runnable onConfigurationChanged`
/// The returned object must be released after use, by calling the [release] method.
static set onConfigurationChanged(Runnable? value) =>
_id_onConfigurationChanged.set(_class, Runnable.type, value);

View File

@@ -1,14 +0,0 @@
import 'package:floating/floating.dart';
extension RationalExt on Rational {
/// Checks whether given [Rational] instance fits into Android requirements
/// or not.
///
/// Android docs specified boundaries as inclusive.
bool get fitsInAndroidRequirements {
final aspectRatio = numerator / denominator;
const min = 1 / 2.39;
const max = 2.39;
return (min <= aspectRatio) && (aspectRatio <= max);
}
}

View File

@@ -17,9 +17,9 @@ import 'package:PiliPlus/pages/common/publish/publish_route.dart';
import 'package:PiliPlus/pages/contact/view.dart';
import 'package:PiliPlus/pages/fav_panel/view.dart';
import 'package:PiliPlus/pages/share/view.dart';
import 'package:PiliPlus/utils/android/android_helper.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/extension/context_ext.dart';
import 'package:PiliPlus/utils/extension/extension.dart';
import 'package:PiliPlus/utils/extension/size_ext.dart';
import 'package:PiliPlus/utils/extension/string_ext.dart';
import 'package:PiliPlus/utils/feed_back.dart';
@@ -30,7 +30,6 @@ import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/url_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:collection/collection.dart';
import 'package:floating/floating.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -187,22 +186,26 @@ abstract final class PageUtils {
);
}
static void enterPip({int? width, int? height, bool isAuto = false}) {
if (width != null && height != null) {
Rational aspectRatio = Rational(width, height);
aspectRatio = aspectRatio.fitsInAndroidRequirements
? aspectRatio
: height > width
? const Rational.vertical()
: const Rational.landscape();
Floating.enable(
isAuto
? AutoEnable(aspectRatio: aspectRatio)
: EnableManual(aspectRatio: aspectRatio),
);
} else {
Floating.enable(isAuto ? const AutoEnable() : const EnableManual());
static bool _fitsInAndroidRequirements(int width, int height) {
final aspectRatio = width / height;
const min = 1 / 2.39;
const max = 2.39;
return (min <= aspectRatio) && (aspectRatio <= max);
}
static void enterPip({int? width, int? height, bool autoEnter = false}) {
if (width != null &&
height != null &&
!_fitsInAndroidRequirements(width, height)) {
if (height > width) {
width = 9;
height = 16;
} else {
width = 16;
height = 9;
}
}
PiliAndroidHelper.enterPip(width ?? 16, height ?? 9, autoEnter);
}
static Future<void> pushDynDetail(

View File

@@ -591,15 +591,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.1"
floating:
dependency: "direct main"
description:
path: "."
ref: dev
resolved-ref: fdd44a501d9717ebab04e8004fca2818a6bed236
url: "https://github.com/bggRGjQaUbCoE/floating.git"
source: git
version: "3.0.0"
flutter:
dependency: "direct main"
description: flutter

View File

@@ -83,10 +83,6 @@ dependencies:
fixnum: ^1.1.1
fl_chart: ^1.0.0
flex_seed_scheme: ^4.0.1
floating:
git:
url: https://github.com/bggRGjQaUbCoE/floating.git
ref: dev
flutter_cache_manager: ^3.4.1
flutter_displaymode: ^0.7.0
flutter_html: ^3.0.0-beta.2