From ccebcd2fdca4ef54fa034faaf9735e428da1991b Mon Sep 17 00:00:00 2001 From: lyswhut Date: Sat, 2 Nov 2024 20:50:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=93=9D=E7=89=99=E6=AD=8C?= =?UTF-8?q?=E8=AF=8D=E6=94=AF=E6=8C=81=EF=BC=88#615=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/toside/music/mobile/lyric/Lyric.java | 118 ++++++++++++++---- .../toside/music/mobile/lyric/LyricEvent.java | 5 +- .../music/mobile/lyric/LyricModule.java | 15 ++- package-lock.json | 12 +- package.json | 2 +- publish/changeLog.md | 4 + src/config/constant.ts | 1 + src/config/defaultSetting.ts | 1 + src/core/common.ts | 4 +- src/core/desktopLyric.ts | 30 ++++- src/core/init/player/index.ts | 2 +- src/core/init/player/lyric.ts | 35 +++++- src/core/init/player/playStatus.ts | 2 +- src/core/init/player/player.ts | 2 +- src/core/player/playInfo.ts | 4 + src/lang/en_us.json | 1 + src/lang/zh_cn.json | 1 + src/plugins/player/playList.ts | 63 ++++++---- src/plugins/player/utils.ts | 4 + .../settings/Player/IsShowBluetoothLyric.tsx | 44 +++++++ .../Views/Setting/settings/Player/index.tsx | 2 + .../settings/SettingPlaybackRate.tsx | 4 +- src/store/player/action.ts | 3 + src/store/player/state.ts | 4 + src/types/app_setting.d.ts | 5 + src/utils/nativeModules/lyricDesktop.ts | 53 ++++---- src/utils/tools.ts | 14 +++ 27 files changed, 332 insertions(+), 103 deletions(-) create mode 100644 src/screens/Home/Views/Setting/settings/Player/IsShowBluetoothLyric.tsx diff --git a/android/app/src/main/java/cn/toside/music/mobile/lyric/Lyric.java b/android/app/src/main/java/cn/toside/music/mobile/lyric/Lyric.java index f93fd84b7..3503d23ca 100644 --- a/android/app/src/main/java/cn/toside/music/mobile/lyric/Lyric.java +++ b/android/app/src/main/java/cn/toside/music/mobile/lyric/Lyric.java @@ -7,8 +7,10 @@ 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; + 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 void onReceive(Context context, Intent intent) { 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 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 @@ private void handleScreenOff() { } 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 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) line.get("extendedLyrics")); + setCurrentLyric((String) line.get("text"), (ArrayList) 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 void showLyric(Bundle options, Promise promise) { 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 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 void setLyric(String lyric, String translation, String romaLyric) { @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 void onSetLyric(List lines) { @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 void toggleRoma(boolean isShowRoma) { } 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); } } diff --git a/android/app/src/main/java/cn/toside/music/mobile/lyric/LyricEvent.java b/android/app/src/main/java/cn/toside/music/mobile/lyric/LyricEvent.java index 3e0624b64..fc806a06b 100644 --- a/android/app/src/main/java/cn/toside/music/mobile/lyric/LyricEvent.java +++ b/android/app/src/main/java/cn/toside/music/mobile/lyric/LyricEvent.java @@ -1,7 +1,5 @@ package cn.toside.music.mobile.lyric; -import android.util.Log; - import androidx.annotation.Nullable; import com.facebook.react.bridge.ReactApplicationContext; @@ -10,12 +8,13 @@ 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); diff --git a/android/app/src/main/java/cn/toside/music/mobile/lyric/LyricModule.java b/android/app/src/main/java/cn/toside/music/mobile/lyric/LyricModule.java index 05a29b042..384e9824d 100644 --- a/android/app/src/main/java/cn/toside/music/mobile/lyric/LyricModule.java +++ b/android/app/src/main/java/cn/toside/music/mobile/lyric/LyricModule.java @@ -66,14 +66,21 @@ public void removeListeners(Integer count) { } @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); } diff --git a/package-lock.json b/package-lock.json index 54130c205..0533899e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "react-native-pager-view": "6.3.0", "react-native-quick-base64": "^2.1.2", "react-native-quick-md5": "^3.0.6", - "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": { @@ -10873,8 +10873,8 @@ }, "node_modules/react-native-track-player": { "version": "2.1.2", - "resolved": "git+ssh://git@github.com/lyswhut/react-native-track-player.git#409bfef00c16eb08f2aea35dfaf0b2fc40be0434", - "integrity": "sha512-GyFA7xRWX6eU8/0u/J/g9/RLc4h/NnnmXHNReTLwi3/lLpP+IcAZxrlRiuPdlHelr7y/bxA7pMZEntPXrRRxVw==", + "resolved": "git+ssh://git@github.com/lyswhut/react-native-track-player.git#930681ab40fdb50f3d0eedff6ecd29b1666fd3ff", + "integrity": "sha512-i268ePGmDQvImXWi7mRttFnyA3ReIoMY12/40g4qj1OQgkDScDi/00O0QCYfL3pHCQc36pfi40GWNLM4i02Ziw==", "license": "Apache-2.0", "peerDependencies": { "react": ">=16.8.6", @@ -21482,9 +21482,9 @@ } }, "react-native-track-player": { - "version": "git+ssh://git@github.com/lyswhut/react-native-track-player.git#409bfef00c16eb08f2aea35dfaf0b2fc40be0434", - "integrity": "sha512-GyFA7xRWX6eU8/0u/J/g9/RLc4h/NnnmXHNReTLwi3/lLpP+IcAZxrlRiuPdlHelr7y/bxA7pMZEntPXrRRxVw==", - "from": "react-native-track-player@github:lyswhut/react-native-track-player#409bfef00c16eb08f2aea35dfaf0b2fc40be0434", + "version": "git+ssh://git@github.com/lyswhut/react-native-track-player.git#930681ab40fdb50f3d0eedff6ecd29b1666fd3ff", + "integrity": "sha512-i268ePGmDQvImXWi7mRttFnyA3ReIoMY12/40g4qj1OQgkDScDi/00O0QCYfL3pHCQc36pfi40GWNLM4i02Ziw==", + "from": "react-native-track-player@github:lyswhut/react-native-track-player#930681ab40fdb50f3d0eedff6ecd29b1666fd3ff", "requires": {} }, "react-native-vector-icons": { diff --git a/package.json b/package.json index 419cc1d73..a2ae3d32f 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "react-native-pager-view": "6.3.0", "react-native-quick-base64": "^2.1.2", "react-native-quick-md5": "^3.0.6", - "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": { diff --git a/publish/changeLog.md b/publish/changeLog.md index 2175b86c2..594201d34 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -1,3 +1,7 @@ +### 新增 + +- 新增蓝牙歌词支持,可以去 设置-播放设置-显示蓝牙歌词 启用(#615) + ### 优化 - 首次使用的提示窗口可以点击背景或者返回键关闭(#577) diff --git a/src/config/constant.ts b/src/config/constant.ts index 284274a44..b70b252aa 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -73,6 +73,7 @@ export const storageDataPrefix = { theme: '@theme', cheatTip: '@cheat_tip', + remoteLyricTip: '@remote_lyric_tip', dislikeList: '@dislike_list', diff --git a/src/config/defaultSetting.ts b/src/config/defaultSetting.ts index 4fbdce95a..56f7a8493 100644 --- a/src/config/defaultSetting.ts +++ b/src/config/defaultSetting.ts @@ -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, diff --git a/src/core/common.ts b/src/core/common.ts index 83e6e6311..20e8567f4 100644 --- a/src/core/common.ts +++ b/src/core/common.ts @@ -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() diff --git a/src/core/desktopLyric.ts b/src/core/desktopLyric.ts index 613888a83..3ee30a380 100644 --- a/src/core/desktopLyric.ts +++ b/src/core/desktopLyric.ts @@ -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) + }) + } + } +} diff --git a/src/core/init/player/index.ts b/src/core/init/player/index.ts index 759bdc411..ccdd609ea 100644 --- a/src/core/init/player/index.ts +++ b/src/core/init/player/index.ts @@ -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) } diff --git a/src/core/init/player/lyric.ts b/src/core/init/player/lyric.ts index 32426f139..06014abf4 100644 --- a/src/core/init/player/lyric.ts +++ b/src/core/init/player/lyric.ts @@ -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) diff --git a/src/core/init/player/playStatus.ts b/src/core/init/player/playStatus.ts index f88067b7e..742f205b0 100644 --- a/src/core/init/player/playStatus.ts +++ b/src/core/init/player/playStatus.ts @@ -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) diff --git a/src/core/init/player/player.ts b/src/core/init/player/player.ts index 200421232..317156a4d 100644 --- a/src/core/init/player/player.ts +++ b/src/core/init/player/player.ts @@ -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) } } diff --git a/src/core/player/playInfo.ts b/src/core/player/playInfo.ts index d1a29ff9e..b22f0add6 100644 --- a/src/core/player/playInfo.ts +++ b/src/core/player/playInfo.ts @@ -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) } diff --git a/src/lang/en_us.json b/src/lang/en_us.json index e658e2f12..d47e27482 100644 --- a/src/lang/en_us.json +++ b/src/lang/en_us.json @@ -346,6 +346,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)", diff --git a/src/lang/zh_cn.json b/src/lang/zh_cn.json index c6bdc3aac..bc0241614 100644 --- a/src/lang/zh_cn.json +++ b/src/lang/zh_cn.json @@ -346,6 +346,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": "显示歌词翻译(如果可用)", diff --git a/src/plugins/player/playList.ts b/src/plugins/player/playList.ts index 1364ca4b4..1432b338d 100644 --- a/src/plugins/player/playList.ts +++ b/src/plugins/player/playList.ts @@ -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) { + debounce(fn: (musicInfo: LX.Player.MusicInfo, lyric?: string) => void | Promise) { // 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) } }) }) diff --git a/src/plugins/player/utils.ts b/src/plugins/player/utils.ts index f1d5cf8d4..1fe278d32 100644 --- a/src/plugins/player/utils.ts +++ b/src/plugins/player/utils.ts @@ -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)]) diff --git a/src/screens/Home/Views/Setting/settings/Player/IsShowBluetoothLyric.tsx b/src/screens/Home/Views/Setting/settings/Player/IsShowBluetoothLyric.tsx new file mode 100644 index 000000000..5dab462b1 --- /dev/null +++ b/src/screens/Home/Views/Setting/settings/Player/IsShowBluetoothLyric.tsx @@ -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 ( + + + + ) +}) + + +const styles = createStyle({ + content: { + marginTop: 5, + }, +}) + diff --git a/src/screens/Home/Views/Setting/settings/Player/index.tsx b/src/screens/Home/Views/Setting/settings/Player/index.tsx index 706810c0b..6bd6666ca 100644 --- a/src/screens/Home/Views/Setting/settings/Player/index.tsx +++ b/src/screens/Home/Views/Setting/settings/Player/index.tsx @@ -6,6 +6,7 @@ import PlayHighQuality from './PlayHighQuality' 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' @@ -23,6 +24,7 @@ export default memo(() => { + diff --git a/src/screens/PlayDetail/components/SettingPopup/settings/SettingPlaybackRate.tsx b/src/screens/PlayDetail/components/SettingPopup/settings/SettingPlaybackRate.tsx index 39aa0e774..703eb7a3e 100644 --- a/src/screens/PlayDetail/components/SettingPopup/settings/SettingPlaybackRate.tsx +++ b/src/screens/PlayDetail/components/SettingPopup/settings/SettingPlaybackRate.tsx @@ -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 }) diff --git a/src/store/player/action.ts b/src/store/player/action.ts index 7bc51dea6..6788c9daa 100644 --- a/src/store/player/action.ts +++ b/src/store/player/action.ts @@ -107,4 +107,7 @@ export default { setLoadErrorPicUrl(url: string) { state.loadErrorPicUrl = url }, + setLastLyric(lrc?: string) { + state.lastLyric = lrc + }, } diff --git a/src/store/player/state.ts b/src/store/player/state.ts index 0004124e8..9505cf603 100644 --- a/src/store/player/state.ts +++ b/src/store/player/state.ts @@ -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, } diff --git a/src/types/app_setting.d.ts b/src/types/app_setting.d.ts index c71351d2b..de74ff2f4 100644 --- a/src/types/app_setting.d.ts +++ b/src/types/app_setting.d.ts @@ -186,6 +186,11 @@ declare global { */ 'player.isS2t': boolean + /** + * 是否启用蓝牙歌词 + */ + 'player.isShowBluetoothLyric': boolean + /** * 播放详情页-是否缩放当前播放的歌词行 */ diff --git a/src/utils/nativeModules/lyricDesktop.ts b/src/utils/nativeModules/lyricDesktop.ts index 1063807e4..f8eb8e8c6 100644 --- a/src/utils/nativeModules/lyricDesktop.ts +++ b/src/utils/nativeModules/lyricDesktop.ts @@ -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 => { - 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 => { - if (!isShowLyric) return Promise.resolve() - return LyricModule.hideLyric().then(() => { - isShowLyric = false - }) +export const hideDesktopLyricView = async(): Promise => { + return LyricModule.hideDesktopLyric() } @@ -107,7 +107,6 @@ export const hideLyric = async(): Promise => { * @returns {Promise} Promise */ export const play = async(time: number): Promise => { - if (!isShowLyric) return Promise.resolve() return LyricModule.play(time) } @@ -115,7 +114,6 @@ export const play = async(time: number): Promise => { * pause lyric */ export const pause = async(): Promise => { - if (!isShowLyric) return Promise.resolve() return LyricModule.pause() } @@ -126,12 +124,10 @@ export const pause = async(): Promise => { * @param romalrc lyric translation */ export const setLyric = async(lyric: string, translation: string, romalrc: string): Promise => { - if (!isShowLyric) return Promise.resolve() return LyricModule.setLyric(lyric, translation || '', romalrc || '') } export const setPlaybackRate = async(rate: number): Promise => { - // if (!isShowLyric) return Promise.resolve() return LyricModule.setPlaybackRate(rate) } @@ -140,7 +136,6 @@ export const setPlaybackRate = async(rate: number): Promise => { * @param isShowTranslation is show translation */ export const toggleTranslation = async(isShowTranslation: boolean): Promise => { - // if (!isShowLyric) return Promise.resolve() return LyricModule.toggleTranslation(isShowTranslation) } @@ -149,7 +144,6 @@ export const toggleTranslation = async(isShowTranslation: boolean): Promise => { - // if (!isShowLyric) return Promise.resolve() return LyricModule.toggleRoma(isShowRoma) } @@ -158,7 +152,6 @@ export const toggleRoma = async(isShowRoma: boolean): Promise => { * @param isLock is lock lyric window */ export const toggleLock = async(isLock: boolean): Promise => { - if (!isShowLyric) return Promise.resolve() return LyricModule.toggleLock(isLock) } @@ -169,7 +162,6 @@ export const toggleLock = async(isLock: boolean): Promise => { * @param shadowColor */ export const setColor = async(unplayColor: string, playedColor: string, shadowColor: string): Promise => { - 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 => { - if (!isShowLyric) return Promise.resolve() return LyricModule.setAlpha(getAlpha(alpha)) } @@ -187,42 +178,34 @@ export const setAlpha = async(alpha: number): Promise => { * @param size text size */ export const setTextSize = async(size: number): Promise => { - if (!isShowLyric) return Promise.resolve() return LyricModule.setTextSize(getTextSize(size)) } export const setShowToggleAnima = async(isShowToggleAnima: boolean): Promise => { - if (!isShowLyric) return Promise.resolve() return LyricModule.setShowToggleAnima(isShowToggleAnima) } export const setSingleLine = async(isSingleLine: boolean): Promise => { - if (!isShowLyric) return Promise.resolve() return LyricModule.setSingleLine(isSingleLine) } export const setPosition = async(x: number, y: number): Promise => { - if (!isShowLyric) return Promise.resolve() return LyricModule.setPosition(x, y) } export const setMaxLineNum = async(maxLineNum: number): Promise => { - if (!isShowLyric) return Promise.resolve() return LyricModule.setMaxLineNum(maxLineNum) } export const setWidth = async(width: number): Promise => { - if (!isShowLyric) return Promise.resolve() return LyricModule.setWidth(width) } // export const fixViewPosition = async(): Promise => { -// 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 => { - 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() + } +} + diff --git a/src/utils/tools.ts b/src/utils/tools.ts index 193e113f7..6262bb08c 100644 --- a/src/utils/tools.ts +++ b/src/utils/tools.ts @@ -559,3 +559,17 @@ export const cheatTip = async() => { void saveData(storageDataPrefix.cheatTip, true) }) } + +export const remoteLyricTip = async() => { + const isRead = await getData(storageDataPrefix.remoteLyricTip) + if (isRead) return + + return tipDialog({ + title: '有点温馨的提示', + message: '若你将本功能用于汽车,请记住这个:\n道路千万条,安全第一条!\n道路千万条,安全第一条!!\n道路千万条,安全第一条!!!', + btnText: '我知道了 (Close)', + bgClose: true, + }).then(() => { + void saveData(storageDataPrefix.remoteLyricTip, true) + }) +}