diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index dbf4550..cf9b8a1 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,6 +9,7 @@
+
= Build.VERSION_CODES.M) {
+ PowerManager powerManager = (PowerManager) context.getSystemService(POWER_SERVICE);
+ return powerManager.isIgnoringBatteryOptimizations(packageName);
+ } else {
+ return true;
+ }
+ }
+
+ public static boolean requestIgnoreBatteryOptimization(Context context, String packageName) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ @SuppressLint("BatteryLife") Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+ intent.setData(Uri.parse("package:" + packageName));
+ try {
+ context.startActivity(intent);
+ return true;
+ } catch (Exception ignored) {}
+ try {
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ return true;
+ } catch (Exception ignored) {}
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/android/app/src/main/java/cn/toside/music/mobile/utils/UtilsModule.java b/android/app/src/main/java/cn/toside/music/mobile/utils/UtilsModule.java
index ab57473..aaec678 100644
--- a/android/app/src/main/java/cn/toside/music/mobile/utils/UtilsModule.java
+++ b/android/app/src/main/java/cn/toside/music/mobile/utils/UtilsModule.java
@@ -255,7 +255,7 @@ public class UtilsModule extends ReactContextBaseJavaModule {
// https://blog.51cto.com/u_15298568/3121162
@ReactMethod
- public void openNotificationPermissionActivity() {
+ public void openNotificationPermissionActivity(Promise promise) {
Intent intent = new Intent();
String packageName = reactContext.getApplicationContext().getPackageName();
@@ -268,7 +268,12 @@ public class UtilsModule extends ReactContextBaseJavaModule {
intent.putExtra("app_uid", reactContext.getApplicationContext().getApplicationInfo().uid);
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- reactContext.startActivity(intent);
+ try {
+ reactContext.startActivity(intent);
+ promise.resolve(true);
+ } catch (Exception ignore) {
+ promise.resolve(false);
+ }
}
@ReactMethod
@@ -371,5 +376,15 @@ public class UtilsModule extends ReactContextBaseJavaModule {
params.putInt("height", rect.height());
promise.resolve(params);
}
+
+ @ReactMethod
+ public void isIgnoringBatteryOptimization(Promise promise) {
+ promise.resolve(BatteryOptimizationUtil.isIgnoringBatteryOptimization(reactContext.getApplicationContext(), reactContext.getPackageName()));
+ }
+
+ @ReactMethod
+ public void requestIgnoreBatteryOptimization(Promise promise) {
+ promise.resolve(BatteryOptimizationUtil.requestIgnoreBatteryOptimization(reactContext.getApplicationContext(), reactContext.getPackageName()));
+ }
}
diff --git a/publish/changeLog.md b/publish/changeLog.md
index 580982d..fc00ed5 100644
--- a/publish/changeLog.md
+++ b/publish/changeLog.md
@@ -1,18 +1,3 @@
-落雪提前祝大家中秋快乐~🥮😘!
-
### 优化
-- 通过歌曲菜单添加不喜欢歌曲时需要二次确认防止手抖
-- 减慢歌词详情页歌词滚动速度
-- 更改应用窗口大小获取方式,尝试解决在某些设备上的背景、弹出菜单显示问题
-- 优化同步功能错误消息提示,因同步服务版本不匹配导致的连接失败现在将区分提示
-
-### 修复
-
-- 修复横屏状态下的歌词滚动位置计算问题
-- 修复切歌时歌词激活行的重置问题
-- 修复更新翻译歌词、罗马音歌词设置后需重启应用才生效的问题,现在更新设置后会立即生效
-
-### 其他
-
-- 更新 React native 到 v0.72.5
+- 添加是否忽略电池优化检查,用于提醒用户添加白名单,确保APP后台播放稳定性
diff --git a/src/config/constant.ts b/src/config/constant.ts
index c878bfb..e1572be 100644
--- a/src/config/constant.ts
+++ b/src/config/constant.ts
@@ -56,6 +56,7 @@ export const storageDataPrefix = {
syncHostHistory: '@sync_host_history',
notificationTipEnable: '@notification_tip_enable',
+ ignoringBatteryOptimizationTipEnable: '@ignoring_battery_optimization_tip_enable',
searchHistoryList: '@search_history_list',
listUpdateInfo: '@list_update_info',
diff --git a/src/core/init/player/playProgress.ts b/src/core/init/player/playProgress.ts
index 60b84b2..0d7c5bc 100644
--- a/src/core/init/player/playProgress.ts
+++ b/src/core/init/player/playProgress.ts
@@ -8,6 +8,7 @@ import BackgroundTimer from 'react-native-background-timer'
import playerState from '@/store/player/state'
import settingState from '@/store/setting/state'
import { onScreenStateChange } from '@/utils/nativeModules/utils'
+import { AppState } from 'react-native'
const delaySavePlayInfo = throttleBackgroundTimer(() => {
void savePlayInfo({
@@ -158,6 +159,10 @@ export default () => {
} else clearUpdateTimeout()
}
+ // 修复在某些设备上屏幕状态改变事件未触发导致的进度条未更新的问题
+ AppState.addEventListener('change', (state) => {
+ if (state == 'active' && !isScreenOn) handleScreenStateChanged('ON')
+ })
global.app_event.on('play', handlePlay)
global.app_event.on('pause', handlePause)
diff --git a/src/core/player/player.ts b/src/core/player/player.ts
index 817ec9e..fca8eea 100644
--- a/src/core/player/player.ts
+++ b/src/core/player/player.ts
@@ -24,7 +24,7 @@ import { requestMsg } from '@/utils/message'
import { getRandom } from '@/utils/common'
import { filterList } from './utils'
import BackgroundTimer from 'react-native-background-timer'
-import { checkNotificationPermission } from '@/utils/tools'
+import { checkIgnoringBatteryOptimization, checkNotificationPermission } from '@/utils/tools'
// import { checkMusicFileAvailable } from '@renderer/utils/music'
@@ -180,6 +180,7 @@ const handleRestorePlay = async(restorePlayInfo: LX.Player.SavedPlayInfo) => {
const handlePlay = async() => {
if (!isInitialized()) {
await checkNotificationPermission()
+ void checkIgnoringBatteryOptimization()
await playerInitial({
volume: settingState.setting['player.volume'],
playRate: settingState.setting['player.playbackRate'],
diff --git a/src/lang/en_us.json b/src/lang/en_us.json
index 5d62c29..81f8489 100644
--- a/src/lang/en_us.json
+++ b/src/lang/en_us.json
@@ -2,6 +2,7 @@
"add_to": "Add to...",
"agree": "Agree",
"agree_go": "To turn on",
+ "agree_to": "Go to settings",
"back": "Back",
"back_home": "Back",
"cancel": "Cancel",
@@ -39,6 +40,8 @@
"dislike": "Dislike",
"duplicate_list_tip": "You have previously favorited the list [{name}], do you want to update the songs?",
"exit_app_tip": "Are you sure you want to quit the app?",
+ "ignoring_battery_optimization_check_tip": "LX Music is not on the list of ignored battery optimization, which may cause the problem of being suspended by the system when playing music in the background. Do you want to add LX Music to the whitelist?",
+ "ignoring_battery_optimization_check_title": "Background running permission setting reminder",
"input_error": "Don't input indiscriminately 😡",
"list_add_btn_title": "Add the song(s) to {name}",
"list_add_tip_exists": "This song already exists in the list, don't click me again~😡",
diff --git a/src/lang/zh_cn.json b/src/lang/zh_cn.json
index 1982ef1..2998177 100644
--- a/src/lang/zh_cn.json
+++ b/src/lang/zh_cn.json
@@ -2,6 +2,7 @@
"add_to": "添加到...",
"agree": "行行行",
"agree_go": "去开启",
+ "agree_to": "去设置",
"back": "返回",
"back_home": "返回桌面",
"cancel": "取消",
@@ -39,6 +40,8 @@
"dislike": "不喜欢",
"duplicate_list_tip": "你之前已收藏过该列表 [{name}],是否需要更新里面的歌曲?",
"exit_app_tip": "确定要退出应用吗?",
+ "ignoring_battery_optimization_check_tip": "LX Music没有在忽略电池优化的名单中,这可能会导致在后台播放音乐时被系统暂停的问题,是否将LX Music加入该白名单中?",
+ "ignoring_battery_optimization_check_title": "后台运行权限设置提醒",
"input_error": "不要乱输好吧😡",
"list_add_btn_title": "把该歌曲添加到 {name}",
"list_add_tip_exists": "列表已经存在这首歌啦,不要再点我啦~😡",
diff --git a/src/screens/Home/Views/Setting/settings/Other/ResourceCache.tsx b/src/screens/Home/Views/Setting/settings/Other/ResourceCache.tsx
index 8c1be82..06671c0 100644
--- a/src/screens/Home/Views/Setting/settings/Other/ResourceCache.tsx
+++ b/src/screens/Home/Views/Setting/settings/Other/ResourceCache.tsx
@@ -5,7 +5,7 @@ import { StyleSheet, View, InteractionManager } from 'react-native'
import SubTitle from '../../components/SubTitle'
import Button from '../../components/Button'
-import { toast, resetNotificationPermissionCheck, confirmDialog } from '@/utils/tools'
+import { toast, resetNotificationPermissionCheck, confirmDialog, resetIgnoringBatteryOptimizationCheck } from '@/utils/tools'
import { getAppCacheSize, clearAppCache } from '@/utils/nativeModules/cache'
import { sizeFormate } from '@/utils'
import { useI18n } from '@/lang'
@@ -39,6 +39,7 @@ export default memo(() => {
clearAppCache(),
clearMusicUrl(),
resetNotificationPermissionCheck(),
+ resetIgnoringBatteryOptimizationCheck(),
]).then(() => {
toast(t('setting_other_cache_clear_success_tip'))
}).finally(() => {
diff --git a/src/utils/nativeModules/utils.ts b/src/utils/nativeModules/utils.ts
index cd58606..e1d0c36 100644
--- a/src/utils/nativeModules/utils.ts
+++ b/src/utils/nativeModules/utils.ts
@@ -1,4 +1,4 @@
-import { NativeEventEmitter, NativeModules } from 'react-native'
+import { AppState, NativeEventEmitter, NativeModules } from 'react-native'
const { UtilsModule } = NativeModules
@@ -29,7 +29,20 @@ export const getDeviceName = async(): Promise => {
export const isNotificationsEnabled = UtilsModule.isNotificationsEnabled as () => Promise
-export const openNotificationPermissionActivity = UtilsModule.openNotificationPermissionActivity as () => Promise
+export const requestNotificationPermission = async() => new Promise((resolve) => {
+ let subscription = AppState.addEventListener('change', (state) => {
+ if (state != 'active') return
+ subscription.remove()
+ setTimeout(() => {
+ void isNotificationsEnabled().then(resolve)
+ }, 1000)
+ })
+ UtilsModule.openNotificationPermissionActivity().then((result: boolean) => {
+ if (result) return
+ subscription.remove()
+ resolve(false)
+ })
+})
export const shareText = async(shareTitle: string, title: string, text: string): Promise => {
UtilsModule.shareText(shareTitle, title, text)
@@ -71,3 +84,22 @@ export const onWindowSizeChange = (callback: (size: { width: number, height: num
eventListener.remove()
}
}
+
+export const isIgnoringBatteryOptimization = async(): Promise => {
+ return UtilsModule.isIgnoringBatteryOptimization()
+}
+
+export const requestIgnoreBatteryOptimization = async() => new Promise((resolve) => {
+ let subscription = AppState.addEventListener('change', (state) => {
+ if (state != 'active') return
+ subscription.remove()
+ setTimeout(() => {
+ void isIgnoringBatteryOptimization().then(resolve)
+ }, 1000)
+ })
+ UtilsModule.requestIgnoreBatteryOptimization().then((result: boolean) => {
+ if (result) return
+ subscription.remove()
+ resolve(false)
+ })
+})
diff --git a/src/utils/tools.ts b/src/utils/tools.ts
index 7980351..9aa76bc 100644
--- a/src/utils/tools.ts
+++ b/src/utils/tools.ts
@@ -4,7 +4,7 @@ import Clipboard from '@react-native-clipboard/clipboard'
import { storageDataPrefix } from '@/config/constant'
import { gzipFile, unGzipFile } from '@/utils/nativeModules/gzip'
import { temporaryDirectoryPath, unlink } from '@/utils/fs'
-import { getSystemLocales, isNotificationsEnabled, openNotificationPermissionActivity, readFile, shareText, writeFile } from '@/utils/nativeModules/utils'
+import { getSystemLocales, isIgnoringBatteryOptimization, isNotificationsEnabled, requestNotificationPermission, readFile, requestIgnoreBatteryOptimization, shareText, writeFile } from '@/utils/nativeModules/utils'
import musicSdk from '@/utils/musicSdk'
import { getData, removeData, saveData } from '@/plugins/storage'
import BackgroundTimer from 'react-native-background-timer'
@@ -202,35 +202,89 @@ export const checkNotificationPermission = async() => {
if (isHide != null) return
const enabled = await isNotificationsEnabled()
if (enabled) return
- Alert.alert(
- global.i18n.t('notifications_check_title'),
- global.i18n.t('notifications_check_tip'),
- [
- {
- text: global.i18n.t('never_show'),
- onPress: () => {
- void saveData(storageDataPrefix.notificationTipEnable, '1')
- toast(global.i18n.t('disagree_tip'))
+ return new Promise((resolve) => {
+ Alert.alert(
+ global.i18n.t('notifications_check_title'),
+ global.i18n.t('notifications_check_tip'),
+ [
+ {
+ text: global.i18n.t('never_show'),
+ onPress: () => {
+ void saveData(storageDataPrefix.notificationTipEnable, '1')
+ toast(global.i18n.t('disagree_tip'))
+ resolve()
+ },
},
- },
- {
- text: global.i18n.t('disagree'),
- onPress: () => {
- toast(global.i18n.t('disagree_tip'))
+ {
+ text: global.i18n.t('disagree'),
+ onPress: () => {
+ toast(global.i18n.t('disagree_tip'))
+ resolve()
+ },
},
- },
- {
- text: global.i18n.t('agree_go'),
- onPress: () => {
- void openNotificationPermissionActivity()
+ {
+ text: global.i18n.t('agree_go'),
+ onPress: () => {
+ requestAnimationFrame(() => {
+ void requestNotificationPermission().then((result) => {
+ if (!result) toast(global.i18n.t('disagree_tip'))
+ resolve()
+ })
+ })
+ },
},
- },
- ],
- )
+ ],
+ )
+ })
+}
+
+
+export const checkIgnoringBatteryOptimization = async() => {
+ const isHide = await getData(storageDataPrefix.ignoringBatteryOptimizationTipEnable)
+ if (isHide != null) return
+ const enabled = await isIgnoringBatteryOptimization()
+ if (enabled) return
+ return new Promise((resolve) => {
+ Alert.alert(
+ global.i18n.t('ignoring_battery_optimization_check_title'),
+ global.i18n.t('ignoring_battery_optimization_check_tip'),
+ [
+ {
+ text: global.i18n.t('never_show'),
+ onPress: () => {
+ void saveData(storageDataPrefix.ignoringBatteryOptimizationTipEnable, '1')
+ toast(global.i18n.t('disagree_tip'))
+ resolve()
+ },
+ },
+ {
+ text: global.i18n.t('disagree'),
+ onPress: () => {
+ toast(global.i18n.t('disagree_tip'))
+ resolve()
+ },
+ },
+ {
+ text: global.i18n.t('agree_to'),
+ onPress: () => {
+ requestAnimationFrame(() => {
+ void requestIgnoreBatteryOptimization().then((result) => {
+ if (!result) toast(global.i18n.t('disagree_tip'))
+ resolve()
+ })
+ })
+ },
+ },
+ ],
+ )
+ })
}
export const resetNotificationPermissionCheck = async() => {
return removeData(storageDataPrefix.notificationTipEnable)
}
+export const resetIgnoringBatteryOptimizationCheck = async() => {
+ return removeData(storageDataPrefix.ignoringBatteryOptimizationTipEnable)
+}
export const shareMusic = (shareType: LX.ShareType, downloadFileName: LX.AppSetting['download.fileName'], musicInfo: LX.Music.MusicInfo) => {
const name = musicInfo.name