Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
ikun0014 2024-11-03 11:54:36 +08:00
commit 65e8af4d6b
27 changed files with 342 additions and 11810 deletions

View File

@ -1,4 +1,4 @@
package com.ikunshare.music.mobile.lyric;
package cn.toside.music.mobile.lyric;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -7,8 +7,10 @@ import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import java.util.ArrayList;
import java.util.HashMap;
@ -20,12 +22,14 @@ public class Lyric extends LyricPlayer {
LyricEvent lyricEvent = null;
ReactApplicationContext reactAppContext;
boolean isShowLyric = false;
// String lastText = "LX Music Mod ^-^";
boolean isRunPlayer = false;
// String lastText = "LX Music ^-^";
int lastLine = 0;
List lines = new ArrayList();
boolean isShowTranslation;
boolean isShowRoma;
boolean isShowLyricView = false;
boolean isSendLyricTextEvent = false;
String lyricText = "";
String translationText = "";
String romaLyricText = "";
@ -36,6 +40,7 @@ public class Lyric extends LyricPlayer {
this.isShowRoma = isShowRoma;
this.playbackRate = playbackRate;
registerScreenBroadcastReceiver();
// checkA2DPConnection(reactContext);
}
private void registerScreenBroadcastReceiver() {
@ -65,8 +70,40 @@ public class Lyric extends LyricPlayer {
reactAppContext.registerReceiver(screenOnOffReceiver, theFilter);
}
// private void checkA2DPConnection(Context context) {
// BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) {
// bluetoothAdapter.getProfileProxy(context, new BluetoothProfile.ServiceListener() {
// @Override
// public void onServiceConnected(int profile, BluetoothProfile proxy) {
// if (profile == BluetoothProfile.A2DP) {
// List<BluetoothDevice> connectedDevices = proxy.getConnectedDevices();
// if (!connectedDevices.isEmpty()) {
// System.out.println("已连接的 A2DP 媒体设备:");
// for (BluetoothDevice device : connectedDevices) {
// System.out.println("设备名称: " + "地址: " + device.getAddress());
// }
// } else {
// System.out.println("没有连接的 A2DP 媒体设备");
// }
// }
// bluetoothAdapter.closeProfileProxy(profile, proxy);
// }
// @Override
// public void onServiceDisconnected(int profile) {
// // 服务断开时的处理
// System.out.println("蓝牙服务断开时的处理");
// }
// }, BluetoothProfile.A2DP);
// } else {
// System.out.println("蓝牙未开启或设备不支持蓝牙");
// }
// }
private void handleScreenOff() {
if (!isShowLyric) return;
if (!isRunPlayer || !isShowLyricView) return;
setTempPause(true);
if (lyricView != null) {
@ -77,30 +114,59 @@ public class Lyric extends LyricPlayer {
}
private void handleScreenOn() {
if (!isShowLyric) return;
if (!isRunPlayer || !isShowLyricView) return;
if (lyricView == null) lyricView = new LyricView(reactAppContext, lyricEvent);
lyricView.runOnUiThread(() -> {
lyricView.showLyricView();
setViewLyric(lastLine);
handleGetCurrentLyric(lastLine);
setTempPause(false);
});
}
private void setViewLyric(int lineNum) {
private void pausePlayer() {
if (!isRunPlayer || isShowLyricView || isSendLyricTextEvent) return;
isRunPlayer = false;
this.pause();
}
private void setCurrentLyric(String lyric, ArrayList<String> extendedLyrics) {
if (isShowLyricView && lyricView != null) {
lyricView.setLyric(lyric, extendedLyrics);
}
if (isSendLyricTextEvent) {
WritableMap params = Arguments.createMap();
params.putString("text", lyric);
params.putArray("extendedLyrics", Arguments.makeNativeArray(extendedLyrics));
lyricEvent.sendEvent(lyricEvent.LYRIC_Line_PLAY, params);
}
}
private void handleGetCurrentLyric(int lineNum) {
lastLine = lineNum;
if (lyricView == null) return;
if (lineNum >= 0 && lineNum < lines.size()) {
HashMap line = (HashMap) lines.get(lineNum);
if (line != null) {
lyricView.setLyric((String) line.get("text"), (ArrayList<String>) line.get("extendedLyrics"));
setCurrentLyric((String) line.get("text"), (ArrayList<String>) line.get("extendedLyrics"));
return;
}
}
lyricView.setLyric("", new ArrayList<>(0));
setCurrentLyric("", new ArrayList<>(0));
}
public void showLyric(Bundle options, Promise promise) {
public void setSendLyricTextEvent(boolean isSend) {
if (isSendLyricTextEvent == isSend) return;
isSendLyricTextEvent = isSend;
if (isSend) {
if (lyricEvent == null) lyricEvent = new LyricEvent(reactAppContext);
isRunPlayer = true;
} else {
pausePlayer();
}
}
public void showDesktopLyric(Bundle options, Promise promise) {
if (isShowLyricView) return;
if (lyricEvent == null) lyricEvent = new LyricEvent(reactAppContext);
isShowLyricView = true;
if (lyricView == null) lyricView = new LyricView(reactAppContext, lyricEvent);
try {
lyricView.showLyricView(options);
@ -109,24 +175,26 @@ public class Lyric extends LyricPlayer {
Log.e("Lyric", e.getMessage());
return;
}
isShowLyric = true;
isRunPlayer = true;
promise.resolve(null);
}
public void hideLyric() {
this.pause();
public void hideDesktopLyric() {
if (!isShowLyricView) return;
isShowLyricView = false;
pausePlayer();
if (lyricView != null) {
lyricView.destroy();
lyricView = null;
}
isShowLyric = false;
}
private void refreshLyric() {
if (!isRunPlayer) return;
ArrayList<String> extendedLyrics = new ArrayList<>(2);
if (isShowTranslation && !"".equals(translationText)) extendedLyrics.add(translationText);
if (isShowRoma && !"".equals(romaLyricText)) extendedLyrics.add(romaLyricText);
if (lyricView != null) super.setLyric(lyricText, extendedLyrics);
super.setLyric(lyricText, extendedLyrics);
}
public void setLyric(String lyric, String translation, String romaLyric) {
@ -139,7 +207,7 @@ public class Lyric extends LyricPlayer {
@Override
public void onSetLyric(List lines) {
this.lines = lines;
setViewLyric(-1);
handleGetCurrentLyric(-1);
// for (int i = 0; i < lines.size(); i++) {
// HashMap line = (HashMap) lines.get(i);
// Log.d("Lyric", "onSetLyric: " +(String) line.get("text") + " " + line.get("extendedLyrics"));
@ -148,14 +216,14 @@ public class Lyric extends LyricPlayer {
@Override
public void onPlay(int lineNum) {
setViewLyric(lineNum);
handleGetCurrentLyric(lineNum);
// Log.d("Lyric", lineNum + " " + text + " " + (String) line.get("translation"));
}
public void pauseLyric() {
pause();
if (!isShowLyric) return;
if (lyricView != null) lyricView.setLyric("", new ArrayList<>(0));
if (!isRunPlayer) return;
handleGetCurrentLyric(-1);
}
public void lockLyric() {
@ -199,14 +267,22 @@ public class Lyric extends LyricPlayer {
}
public void setPlayedColor(String unplayColor, String playedColor, String shadowColor) {
if (lyricView == null) return;
lyricView.setColor(unplayColor, playedColor, shadowColor);
}
public void setAlpha(float alpha) { lyricView.setAlpha(alpha); }
public void setAlpha(float alpha) {
if (lyricView == null) return;
lyricView.setAlpha(alpha);
}
public void setTextSize(float size) { lyricView.setTextSize(size); }
public void setTextSize(float size) {
if (lyricView == null) return;
lyricView.setTextSize(size);
}
public void setLyricTextPosition(String positionX, String positionY) {
if (lyricView == null) return;
lyricView.setLyricTextPosition(positionX, positionY);
}
}

View File

@ -1,7 +1,5 @@
package com.ikunshare.music.mobile.lyric;
import android.util.Log;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
@ -10,12 +8,13 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
public class LyricEvent {
final String SET_VIEW_POSITION = "set-position";
final String LYRIC_Line_PLAY = "lyric-line-play";
private final ReactApplicationContext reactContext;
LyricEvent(ReactApplicationContext reactContext) { this.reactContext = reactContext; }
public void sendEvent(String eventName, @Nullable WritableMap params) {
Log.d("Lyric", "senEvent: " + eventName);
// Log.d("Lyric", "senEvent: " + eventName);
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);

View File

@ -66,14 +66,21 @@ public class LyricModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void showLyric(ReadableMap data, Promise promise) {
public void showDesktopLyric(ReadableMap data, Promise promise) {
if (lyric == null) lyric = new Lyric(reactContext, isShowTranslation, isShowRoma, playbackRate);
lyric.showLyric(Arguments.toBundle(data), promise);
lyric.showDesktopLyric(Arguments.toBundle(data), promise);
}
@ReactMethod
public void hideLyric(Promise promise) {
if (lyric != null) lyric.hideLyric();
public void hideDesktopLyric(Promise promise) {
if (lyric != null) lyric.hideDesktopLyric();
promise.resolve(null);
}
@ReactMethod
public void setSendLyricTextEvent(boolean isSend, Promise promise) {
if (lyric == null) lyric = new Lyric(reactContext, isShowTranslation, isShowRoma, playbackRate);
lyric.setSendLyricTextEvent(isSend);
promise.resolve(null);
}

11697
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -46,8 +46,8 @@
"dependencies": {
"@craftzdog/react-native-buffer": "^6.0.5",
"@react-native-async-storage/async-storage": "^2.0.0",
"@react-native-clipboard/clipboard": "^1.14.2",
"@react-native-community/slider": "^4.5.3",
"@react-native-clipboard/clipboard": "^1.14.3",
"@react-native-community/slider": "^4.5.5",
"iconv-lite": "^0.6.3",
"lrc-file-parser": "^2.4.1",
"message2call": "^0.1.3",
@ -65,20 +65,20 @@
"react-native-quick-base64": "^2.1.2",
"react-native-quick-md5": "^3.0.6",
"rn-fetch-blob": "^0.12.0",
"react-native-track-player": "github:lyswhut/react-native-track-player#409bfef00c16eb08f2aea35dfaf0b2fc40be0434",
"react-native-track-player": "github:lyswhut/react-native-track-player#930681ab40fdb50f3d0eedff6ecd29b1666fd3ff",
"react-native-vector-icons": "^10.2.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/eslint-parser": "^7.25.1",
"@babel/core": "^7.26.0",
"@babel/eslint-parser": "^7.25.9",
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
"@babel/preset-env": "^7.25.4",
"@babel/runtime": "^7.25.6",
"@react-native/babel-preset": "^0.74.87",
"@react-native/metro-config": "^0.74.87",
"@react-native/typescript-config": "^0.74.87",
"@babel/preset-env": "^7.26.0",
"@babel/runtime": "^7.26.0",
"@react-native/babel-preset": "^0.74.88",
"@react-native/metro-config": "^0.74.88",
"@react-native/typescript-config": "^0.74.88",
"@tsconfig/react-native": "^3.0.5",
"@types/react": "^18.3.8",
"@types/react": "^18.3.12",
"@types/react-native": "^0.72.8",
"@types/react-native-background-timer": "^2.0.2",
"@types/react-native-vector-icons": "^6.4.18",
@ -86,8 +86,8 @@
"changelog-parser": "^3.0.1",
"eslint-config-standard": "^17.1.0",
"eslint-config-standard-with-typescript": "^43.0.1",
"eslint-plugin-react": "^7.36.1",
"eslint-plugin-react-hooks": "^4.6.2",
"typescript": "^5.6.2"
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"typescript": "^5.6.3"
}
}

View File

@ -1,3 +1,7 @@
### 新增
- 新增蓝牙歌词支持,可以去 设置-播放设置-显示蓝牙歌词 启用(#615
### 优化
- 首次使用的提示窗口可以点击背景或者返回键关闭(#577

View File

@ -73,6 +73,7 @@ export const storageDataPrefix = {
theme: '@theme',
cheatTip: '@cheat_tip',
remoteLyricTip: '@remote_lyric_tip',
dislikeList: '@dislike_list',

View File

@ -31,6 +31,7 @@ const defaultSetting: LX.AppSetting = {
'player.isShowLyricRoma': false,
'player.isShowNotificationImage': true,
'player.isS2t': false,
'player.isShowBluetoothLyric': false,
// 'playDetail.isZoomActiveLrc': false,
// 'playDetail.isShowLyricProgressSetting': false,

View File

@ -14,7 +14,7 @@ import { saveData } from '@/plugins/storage'
import { throttle } from '@/utils/common'
import { getSelectedManagedFolder, saveFontSize, saveViewPrevState, setSelectedManagedFolder } from '@/utils/data'
import { showPactModal as handleShowPactModal } from '@/navigation'
import { hideLyric } from '@/utils/nativeModules/lyricDesktop'
import { hideDesktopLyricView } from '@/utils/nativeModules/lyricDesktop'
import { getPersistedUriList, selectManagedFolder } from '@/utils/fs'
@ -57,7 +57,7 @@ export const exitApp = (reason: string) => {
void Promise.all([
hideDesktopLyric(),
destroyPlayer(),
hideLyric(),
hideDesktopLyricView(),
]).finally(() => {
isDestroying = false
utilExitApp()

View File

@ -1,6 +1,7 @@
import {
hideLyric,
showLyric,
hideDesktopLyricView,
showDesktopLyricView,
setSendLyricTextEvent,
setLyric,
play,
pause,
@ -25,10 +26,13 @@ import settingState from '@/store/setting/state'
import playerState from '@/store/player/state'
import { tranditionalize } from '@/utils/simplify-chinese-main'
import { getPosition } from '@/plugins/player'
export {
onLyricLinePlay,
} from '@/utils/nativeModules/lyricDesktop'
export const showDesktopLyric = async() => {
const setting = settingState.setting
await showLyric({
await showDesktopLyricView({
isShowToggleAnima: setting['desktopLyric.showToggleAnima'],
isSingleLine: setting['desktopLyric.isSingleLine'],
isLock: setting['desktopLyric.isLock'],
@ -60,7 +64,7 @@ export const showDesktopLyric = async() => {
}
export const hideDesktopLyric = async() => {
return hideLyric()
return hideDesktopLyricView()
}
export const playDesktopLyric = play
@ -91,3 +95,21 @@ export const openDesktopLyricOverlayPermissionActivity = openOverlayPermissionAc
export const onDesktopLyricPositionChange = onPositionChange
export const showRemoteLyric = async(isSend: boolean) => {
await setSendLyricTextEvent(isSend)
if (isSend) {
let lrc = playerState.musicInfo.lrc ?? ''
let tlrc = playerState.musicInfo.tlrc ?? ''
let rlrc = playerState.musicInfo.rlrc ?? ''
if (settingState.setting['player.isS2t']) {
lrc = tranditionalize(lrc)
tlrc = tranditionalize(tlrc)
}
await setLyric(lrc, tlrc, rlrc)
if (playerState.isPlay && !global.lx.gettingUrlId) {
void getPosition().then(position => {
void play(position * 1000)
})
}
}
}

View File

@ -8,10 +8,10 @@ import initLyric from './lyric'
export default async(setting: LX.AppSetting) => {
await initPlayer(setting)
await initLyric(setting)
await initPlayInfo(setting)
initPlayStatus()
initPlayerEvent()
initWatchList()
initPlayProgress()
await initLyric(setting)
}

View File

@ -1,24 +1,51 @@
import { init as initLyricPlayer, toggleTranslation, toggleRoma, play, pause, stop, setLyric, setPlaybackRate } from '@/core/lyric'
import { updateSetting } from '@/core/common'
import { onDesktopLyricPositionChange, showDesktopLyric } from '@/core/desktopLyric'
import { onDesktopLyricPositionChange, showDesktopLyric, onLyricLinePlay, showRemoteLyric } from '@/core/desktopLyric'
import playerState from '@/store/player/state'
import { updateNowPlayingTitles } from '@/plugins/player/utils'
import { setLastLyric } from '@/core/player/playInfo'
import { state } from '@/plugins/player/playList'
const updateRemoteLyric = async(lrc?: string) => {
setLastLyric(lrc)
if (lrc == null) {
void updateNowPlayingTitles((state.prevDuration || 0) * 1000, playerState.musicInfo.name, playerState.musicInfo.singer ?? '', playerState.musicInfo.album ?? '')
} else {
void updateNowPlayingTitles((state.prevDuration || 0) * 1000, lrc, `${playerState.musicInfo.name}${playerState.musicInfo.singer ? ` - ${playerState.musicInfo.singer}` : ''}`, playerState.musicInfo.album ?? '')
}
}
export default async(setting: LX.AppSetting) => {
await initLyricPlayer()
void setPlaybackRate(setting['player.playbackRate'])
void toggleTranslation(setting['player.isShowLyricTranslation'])
void toggleRoma(setting['player.isShowLyricRoma'])
await Promise.all([
setPlaybackRate(setting['player.playbackRate']),
toggleTranslation(setting['player.isShowLyricTranslation']),
toggleRoma(setting['player.isShowLyricRoma']),
])
if (setting['desktopLyric.enable']) {
showDesktopLyric().catch(() => {
updateSetting({ 'desktopLyric.enable': false })
})
}
if (setting['player.isShowBluetoothLyric']) {
showRemoteLyric(true).catch(() => {
updateSetting({ 'player.isShowBluetoothLyric': false })
})
}
onDesktopLyricPositionChange(position => {
updateSetting({
'desktopLyric.position.x': position.x,
'desktopLyric.position.y': position.y,
})
})
onLyricLinePlay(({ text, extendedLyrics }) => {
if (!text && !state.isPlaying) {
void updateRemoteLyric()
} else {
void updateRemoteLyric(text)
}
})
global.app_event.on('play', play)

View File

@ -19,7 +19,7 @@ export default () => {
const setButtons = () => {
// setPlayerAction(buttons)
if (!playerState.playMusicInfo.musicInfo) return
void updateMetaData(playerState.musicInfo, playerState.isPlay)
void updateMetaData(playerState.musicInfo, playerState.isPlay, playerState.lastLyric)
}
// const updateCollectStatus = async() => {
// // let status = !!playMusicInfo.musicInfo && await checkListExistMusic(LIST_ID_LOVE, playerState.playMusicInfo.musicInfo.id)

View File

@ -40,7 +40,7 @@ export default async(setting: LX.AppSetting) => {
const updatePic = () => {
if (!settingState.setting['player.isShowNotificationImage']) return
if (playerState.playMusicInfo.musicInfo && playerState.musicInfo.pic) {
delayUpdateMusicInfo(playerState.musicInfo)
delayUpdateMusicInfo(playerState.musicInfo, playerState.lastLyric)
}
}

View File

@ -14,6 +14,10 @@ export const setLoadErrorPicUrl = (url: string) => {
playerActions.setLoadErrorPicUrl(url)
}
export const setLastLyric = (lrc?: string) => {
playerActions.setLastLyric(lrc)
}
export const setPlayListId = (listId: string | null) => {
playerActions.setPlayListId(listId)
}

View File

@ -348,6 +348,7 @@
"setting_play_play_quality": "Prioritize playback sound quality (if supported)",
"setting_play_s2t": "Convert the played lyrics to Traditional Chinese",
"setting_play_save_play_time": "Remember playback progress",
"setting_play_show_bluetooth_lyric": "Show bluetooth lyrics",
"setting_play_show_notification_image": "Show song picture in notification bar",
"setting_play_show_roma": "Show lyrics roman (if available)",
"setting_play_show_translation": "Show lyrics translation (if available)",

View File

@ -349,6 +349,7 @@
"setting_play_play_quality": "优先播放的音质(如果支持)",
"setting_play_s2t": "将播放的歌词转繁体",
"setting_play_save_play_time": "记住播放进度",
"setting_play_show_bluetooth_lyric": "显示蓝牙歌词",
"setting_play_show_notification_image": "在通知栏显示歌曲图片",
"setting_play_show_roma": "显示歌词罗马音(如果可用)",
"setting_play_show_translation": "显示歌词翻译(如果可用)",

View File

@ -10,8 +10,10 @@ const list: LX.Player.Track[] = []
const defaultUserAgent = 'Mozilla/5.0 (Linux; Android 10; Pixel 3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Mobile Safari/537.36'
const httpRxp = /^(https?:\/\/.+|\/.+)/
let isPlaying = false
let prevDuration = -1
export const state = {
isPlaying: false,
prevDuration: -1,
}
const formatMusicInfo = (musicInfo: LX.Player.PlayMusic) => {
return 'progress' in musicInfo ? {
@ -106,22 +108,21 @@ export const getCurrentTrack = async() => {
return list[currentTrackIndex]
}
export const updateMetaData = async(musicInfo: LX.Player.MusicInfo, isPlay: boolean, force = false) => {
if (!force && isPlay == isPlaying) {
export const updateMetaData = async(musicInfo: LX.Player.MusicInfo, isPlay: boolean, lyric?: string, force = false) => {
if (!force && isPlay == state.isPlaying) {
const duration = await TrackPlayer.getDuration()
// console.log('currentIsPlaying', prevDuration, duration)
if (prevDuration != duration) {
prevDuration = duration
if (state.prevDuration != duration) {
state.prevDuration = duration
const trackInfo = await getCurrentTrack()
if (trackInfo && musicInfo) {
delayUpdateMusicInfo(musicInfo)
delayUpdateMusicInfo(musicInfo, lyric)
}
}
} else {
const [duration, trackInfo] = await Promise.all([TrackPlayer.getDuration(), getCurrentTrack()])
prevDuration = duration
state.prevDuration = duration
if (trackInfo && musicInfo) {
delayUpdateMusicInfo(musicInfo)
delayUpdateMusicInfo(musicInfo, lyric)
}
}
}
@ -175,7 +176,8 @@ export const playMusic = (musicInfo: LX.Player.PlayMusic, url: string, time: num
// let musicId = null
// let duration = 0
let prevArtwork: string | undefined
const updateMetaInfo = async(mInfo: LX.Player.MusicInfo) => {
const updateMetaInfo = async(mInfo: LX.Player.MusicInfo, lyric?: string) => {
console.log('updateMetaInfo', lyric)
const isShowNotificationImage = settingState.setting['player.isShowNotificationImage']
// const mInfo = formatMusicInfo(musicInfo)
// console.log('+++++updateMusicPic+++++', track.artwork, track.duration)
@ -189,16 +191,25 @@ const updateMetaInfo = async(mInfo: LX.Player.MusicInfo) => {
// duration = global.playInfo.duration || 0
// }
// console.log('+++++updateMetaInfo+++++', mInfo.name)
isPlaying = await TrackPlayer.getState() == State.Playing
state.isPlaying = await TrackPlayer.getState() == State.Playing
let artwork = isShowNotificationImage ? mInfo.pic ?? prevArtwork : undefined
if (mInfo.pic) prevArtwork = mInfo.pic
let name: string
let singer: string
if (!state.isPlaying || lyric == null) {
name = mInfo.name ?? 'Unknow'
singer = mInfo.singer ?? 'Unknow'
} else {
name = lyric
singer = `${mInfo.name}${mInfo.singer ? ` - ${mInfo.singer}` : ''}`
}
await TrackPlayer.updateNowPlayingMetadata({
title: mInfo.name ?? 'Unknow',
artist: mInfo.singer ?? 'Unknow',
title: name,
artist: singer,
album: mInfo.album ?? undefined,
artwork,
duration: prevDuration || 0,
}, isPlaying)
duration: state.prevDuration || 0,
}, state.isPlaying)
}
@ -206,12 +217,13 @@ const updateMetaInfo = async(mInfo: LX.Player.MusicInfo) => {
const debounceUpdateMetaInfoTools = {
updateMetaPromise: Promise.resolve(),
musicInfo: null as LX.Player.MusicInfo | null,
debounce(fn: (musicInfo: LX.Player.MusicInfo) => void | Promise<void>) {
debounce(fn: (musicInfo: LX.Player.MusicInfo, lyric?: string) => void | Promise<void>) {
// let delayTimer = null
let isDelayRun = false
let timer: number | null = null
let _musicInfo: LX.Player.MusicInfo | null = null
return (musicInfo: LX.Player.MusicInfo) => {
let _lyric: string | undefined
return (musicInfo: LX.Player.MusicInfo, lyric?: string) => {
// console.log('debounceUpdateMetaInfoTools', musicInfo)
if (timer) {
BackgroundTimer.clearTimeout(timer)
@ -223,31 +235,34 @@ const debounceUpdateMetaInfoTools = {
// }
if (isDelayRun) {
_musicInfo = musicInfo
_lyric = lyric
timer = BackgroundTimer.setTimeout(() => {
timer = null
let musicInfo = _musicInfo
let lyric = _lyric
_musicInfo = null
_lyric = undefined
if (!musicInfo) return
// isDelayRun = false
void fn(musicInfo)
}, 1000)
void fn(musicInfo, lyric)
}, 500)
} else {
isDelayRun = true
void fn(musicInfo)
void fn(musicInfo, lyric)
BackgroundTimer.setTimeout(() => {
// delayTimer = null
isDelayRun = false
}, 1000)
}, 500)
}
}
},
init() {
return this.debounce(async(musicInfo: LX.Player.MusicInfo) => {
return this.debounce(async(musicInfo: LX.Player.MusicInfo, lyric?: string) => {
this.musicInfo = musicInfo
return this.updateMetaPromise.then(() => {
// console.log('run')
if (this.musicInfo?.id === musicInfo.id) {
this.updateMetaPromise = updateMetaInfo(musicInfo)
this.updateMetaPromise = updateMetaInfo(musicInfo, lyric)
}
})
})

View File

@ -166,6 +166,10 @@ export const setPause = async() => TrackPlayer.pause()
export const setCurrentTime = async(time: number) => TrackPlayer.seekTo(time)
export const setVolume = async(num: number) => TrackPlayer.setVolume(num)
export const setPlaybackRate = async(num: number) => TrackPlayer.setRate(num)
export const updateNowPlayingTitles = async(duration: number, title: string, artist: string, album: string) => {
console.log('set playing titles', duration, title, artist, album)
return TrackPlayer.updateNowPlayingTitles(duration, title, artist, album)
}
export const resetPlay = async() => Promise.all([setPause(), setCurrentTime(0)])

View File

@ -0,0 +1,44 @@
import { updateSetting } from '@/core/common'
import { useI18n } from '@/lang'
import { createStyle, remoteLyricTip } from '@/utils/tools'
import { memo } from 'react'
import { View } from 'react-native'
import { useSettingValue } from '@/store/setting/hook'
import CheckBoxItem from '../../components/CheckBoxItem'
import { showRemoteLyric } from '@/core/desktopLyric'
import { setLastLyric } from '@/core/player/playInfo'
import { updateNowPlayingTitles } from '@/plugins/player/utils'
import playerState from '@/store/player/state'
import { state } from '@/plugins/player/playList'
export default memo(() => {
const t = useI18n()
const isShowBluetoothLyric = useSettingValue('player.isShowBluetoothLyric')
const setShowBluetoothLyric = async(isShowBluetoothLyric: boolean) => {
if (isShowBluetoothLyric) {
await remoteLyricTip()
}
updateSetting({ 'player.isShowBluetoothLyric': isShowBluetoothLyric })
void showRemoteLyric(isShowBluetoothLyric)
if (!isShowBluetoothLyric) {
setLastLyric()
void updateNowPlayingTitles((state.prevDuration || 0) * 1000, playerState.musicInfo.name, playerState.musicInfo.singer ?? '', playerState.musicInfo.album ?? '')
}
}
return (
<View style={styles.content}>
<CheckBoxItem check={isShowBluetoothLyric} onChange={setShowBluetoothLyric} label={t('setting_play_show_bluetooth_lyric')} />
</View>
)
})
const styles = createStyle({
content: {
marginTop: 5,
},
})

View File

@ -6,6 +6,7 @@ import IsPlayHighQuality from './IsPlayHighQuality'
import IsHandleAudioFocus from './IsHandleAudioFocus'
import IsEnableAudioOffload from './IsEnableAudioOffload'
import IsAutoCleanPlayedList from './IsAutoCleanPlayedList'
import IsShowBluetoothLyric from './IsShowBluetoothLyric'
import IsShowNotificationImage from './IsShowNotificationImage'
import IsShowLyricTranslation from './IsShowLyricTranslation'
import IsShowLyricRoma from './IsShowLyricRoma'
@ -25,6 +26,7 @@ export default memo(() => {
{/* <IsPlayHighQuality /> */}
<IsHandleAudioFocus />
<IsEnableAudioOffload />
<IsShowBluetoothLyric />
<IsShowNotificationImage />
<IsShowLyricTranslation />
<IsShowLyricRoma />

View File

@ -37,7 +37,7 @@ export default () => {
value = Math.trunc(value)
const rate = value / 100
void setLyricPlaybackRate(rate)
void updateMetaData(playerState.musicInfo, playerState.isPlay, true) // 更新通知栏的播放速率
void updateMetaData(playerState.musicInfo, playerState.isPlay, playerState.lastLyric, true) // 更新通知栏的播放速率
if (playbackRate == value) return
updateSetting({ 'player.playbackRate': rate })
}
@ -45,7 +45,7 @@ export default () => {
if (settingState.setting['player.playbackRate'] == 1) return
setSliderSize(100)
void setPlaybackRate(1).then(() => {
void updateMetaData(playerState.musicInfo, playerState.isPlay, true) // 更新通知栏的播放速率
void updateMetaData(playerState.musicInfo, playerState.isPlay, playerState.lastLyric, true) // 更新通知栏的播放速率
void setLyricPlaybackRate(1)
})
updateSetting({ 'player.playbackRate': 1 })

View File

@ -107,4 +107,7 @@ export default {
setLoadErrorPicUrl(url: string) {
state.loadErrorPicUrl = url
},
setLastLyric(lrc?: string) {
state.lastLyric = lrc
},
}

View File

@ -34,6 +34,8 @@ export interface InitState {
nowPlayTimeStr: string
maxPlayTimeStr: string
}
lastLyric: string | undefined
}
const state: InitState = {
@ -77,6 +79,8 @@ const state: InitState = {
nowPlayTimeStr: '00:00',
maxPlayTimeStr: '00:00',
},
lastLyric: undefined,
}

View File

@ -186,6 +186,11 @@ declare global {
*/
'player.isS2t': boolean
/**
*
*/
'player.isShowBluetoothLyric': boolean
/**
* -
*/

View File

@ -2,8 +2,6 @@ import { NativeModules, NativeEventEmitter } from 'react-native'
const { LyricModule } = NativeModules
let isShowLyric = false
// export const themes = [
// { id: 'green', value: '#07c556' },
// { id: 'yellow', value: '#fffa12' },
@ -34,11 +32,19 @@ let isShowLyric = false
const getAlpha = (num: number) => num / 100
const getTextSize = (num: number) => num / 10
/**
*
* @param isShow
* @returns
*/
export const setSendLyricTextEvent = async(isSend: boolean) => {
return LyricModule.setSendLyricTextEvent(isSend)
}
/**
* show lyric
*/
export const showLyric = async({
export const showDesktopLyricView = async({
isShowToggleAnima,
isSingleLine,
width,
@ -69,8 +75,7 @@ export const showLyric = async({
textPositionX: LX.AppSetting['desktopLyric.textPosition.x']
textPositionY: LX.AppSetting['desktopLyric.textPosition.y']
}): Promise<void> => {
if (isShowLyric) return Promise.resolve()
return LyricModule.showLyric({
return LyricModule.showDesktopLyric({
isSingleLine,
isShowToggleAnima,
isLock,
@ -85,19 +90,14 @@ export const showLyric = async({
textY: textPositionY.toUpperCase(),
width,
maxLineNum,
}).then(() => {
isShowLyric = true
})
}
/**
* hide lyric
*/
export const hideLyric = async(): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.hideLyric().then(() => {
isShowLyric = false
})
export const hideDesktopLyricView = async(): Promise<void> => {
return LyricModule.hideDesktopLyric()
}
@ -107,7 +107,6 @@ export const hideLyric = async(): Promise<void> => {
* @returns {Promise} Promise
*/
export const play = async(time: number): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.play(time)
}
@ -115,7 +114,6 @@ export const play = async(time: number): Promise<void> => {
* pause lyric
*/
export const pause = async(): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.pause()
}
@ -126,12 +124,10 @@ export const pause = async(): Promise<void> => {
* @param romalrc lyric translation
*/
export const setLyric = async(lyric: string, translation: string, romalrc: string): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.setLyric(lyric, translation || '', romalrc || '')
}
export const setPlaybackRate = async(rate: number): Promise<void> => {
// if (!isShowLyric) return Promise.resolve()
return LyricModule.setPlaybackRate(rate)
}
@ -140,7 +136,6 @@ export const setPlaybackRate = async(rate: number): Promise<void> => {
* @param isShowTranslation is show translation
*/
export const toggleTranslation = async(isShowTranslation: boolean): Promise<void> => {
// if (!isShowLyric) return Promise.resolve()
return LyricModule.toggleTranslation(isShowTranslation)
}
@ -149,7 +144,6 @@ export const toggleTranslation = async(isShowTranslation: boolean): Promise<void
* @param isShowRoma is show roma lyric
*/
export const toggleRoma = async(isShowRoma: boolean): Promise<void> => {
// if (!isShowLyric) return Promise.resolve()
return LyricModule.toggleRoma(isShowRoma)
}
@ -158,7 +152,6 @@ export const toggleRoma = async(isShowRoma: boolean): Promise<void> => {
* @param isLock is lock lyric window
*/
export const toggleLock = async(isLock: boolean): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.toggleLock(isLock)
}
@ -169,7 +162,6 @@ export const toggleLock = async(isLock: boolean): Promise<void> => {
* @param shadowColor
*/
export const setColor = async(unplayColor: string, playedColor: string, shadowColor: string): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.setColor(unplayColor, playedColor, shadowColor)
}
@ -178,7 +170,6 @@ export const setColor = async(unplayColor: string, playedColor: string, shadowCo
* @param alpha text alpha
*/
export const setAlpha = async(alpha: number): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.setAlpha(getAlpha(alpha))
}
@ -187,42 +178,34 @@ export const setAlpha = async(alpha: number): Promise<void> => {
* @param size text size
*/
export const setTextSize = async(size: number): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.setTextSize(getTextSize(size))
}
export const setShowToggleAnima = async(isShowToggleAnima: boolean): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.setShowToggleAnima(isShowToggleAnima)
}
export const setSingleLine = async(isSingleLine: boolean): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.setSingleLine(isSingleLine)
}
export const setPosition = async(x: number, y: number): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.setPosition(x, y)
}
export const setMaxLineNum = async(maxLineNum: number): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.setMaxLineNum(maxLineNum)
}
export const setWidth = async(width: number): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.setWidth(width)
}
// export const fixViewPosition = async(): Promise<void> => {
// if (!isShowLyric) return Promise.resolve()
// return LyricModule.fixViewPosition()
// }
export const setLyricTextPosition = async(textX: LX.AppSetting['desktopLyric.textPosition.x'], textY: LX.AppSetting['desktopLyric.textPosition.y']): Promise<void> => {
if (!isShowLyric) return Promise.resolve()
return LyricModule.setLyricTextPosition(textX.toUpperCase(), textY.toUpperCase())
}
@ -246,3 +229,15 @@ export const onPositionChange = (handler: (position: { x: number, y: number }) =
}
}
export const onLyricLinePlay = (handler: (lineInfo: { text: string, extendedLyrics: string[] }) => void): () => void => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const eventEmitter = new NativeEventEmitter(LyricModule)
const eventListener = eventEmitter.addListener('lyric-line-play', event => {
handler(event as { text: string, extendedLyrics: string[] })
})
return () => {
eventListener.remove()
}
}

View File

@ -566,3 +566,17 @@ export const hitokoto = () => {
return toast(data.hitokoto)
})
}
export const remoteLyricTip = async() => {
const isRead = await getData<boolean>(storageDataPrefix.remoteLyricTip)
if (isRead) return
return tipDialog({
title: '有点温馨的提示',
message: '若你将本功能用于汽车,请记住这个:\n道路千万条安全第一条\n道路千万条安全第一条\n道路千万条安全第一条',
btnText: '我知道了 (Close)',
bgClose: true,
}).then(() => {
void saveData(storageDataPrefix.remoteLyricTip, true)
})
}