From c3bbec22db45253b699b6affc0c7990903688e1b Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:17:42 -0800 Subject: [PATCH 01/11] feat: doc picker --- android/app/src/main/AndroidManifest.xml | 11 +++++--- .../com/noxplay/noxplayer/MainApplication.kt | 26 +++++++++++++++++++ package.json | 1 + .../playlist/BiliSearch/SearchMenu.tsx | 13 ++++++++++ yarn.lock | 5 ++++ 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index fd162103..4508a3c4 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,11 @@ + + + - + diff --git a/android/app/src/main/java/com/noxplay/noxplayer/MainApplication.kt b/android/app/src/main/java/com/noxplay/noxplayer/MainApplication.kt index 613e790b..e5775728 100644 --- a/android/app/src/main/java/com/noxplay/noxplayer/MainApplication.kt +++ b/android/app/src/main/java/com/noxplay/noxplayer/MainApplication.kt @@ -2,6 +2,9 @@ package com.noxplay.noxplayer import android.app.Application import android.content.res.Configuration +import android.net.Uri +import android.provider.MediaStore +import android.util.Log import com.facebook.react.PackageList import com.facebook.react.ReactApplication import com.facebook.react.ReactNativeHost @@ -53,6 +56,29 @@ class MainApplication : Application(), ReactApplication { // If you opted-in for the New Architecture, we load the native entry point for this app. load() } + val uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3AMusic") + Log.d("RNTP", MediaStore.Audio.Media.getContentUri( + MediaStore.VOLUME_EXTERNAL + ).toString()) + Log.d("RNTP", MediaStore.Audio.Media.getContentUri( + MediaStore.VOLUME_INTERNAL + ).toString()) + val query = this.contentResolver.query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + arrayOf( + MediaStore.Audio.Media._ID, + MediaStore.Audio.Media.RELATIVE_PATH, + MediaStore.Audio.Media.DISPLAY_NAME + ), null,null, null) + query?.use { cursor -> + Log.d("RNTP", cursor.count.toString()) + val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) + val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.RELATIVE_PATH) + val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME) + while (cursor.moveToNext()) { + Log.d("RNTP", cursor.getString(idColumn) + cursor.getString(pathColumn) + cursor.getString(nameColumn)) + } + } onApplicationCreate(this) } diff --git a/package.json b/package.json index 0a7c9d16..33c1084b 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "dropbox": "git+https://lovegaoshi@github.com/lovegaoshi/dropbox-sdk-js.git", "expo": "^50.0.7", "expo-clipboard": "~5.0.1", + "expo-document-picker": "~11.10.1", "expo-image": "^1.10.6", "expo-keep-awake": "^12.8.2", "expo-secure-store": "~12.8.1", diff --git a/src/components/playlist/BiliSearch/SearchMenu.tsx b/src/components/playlist/BiliSearch/SearchMenu.tsx index f2aada7d..3ef11cce 100644 --- a/src/components/playlist/BiliSearch/SearchMenu.tsx +++ b/src/components/playlist/BiliSearch/SearchMenu.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { Menu } from 'react-native-paper'; +import * as DocumentPicker from 'expo-document-picker'; import { SEARCH_OPTIONS } from '@enums/Storage'; import { MUSICFREE } from '@utils/mediafetch/musicfree'; @@ -44,6 +45,18 @@ export default ({ title={`MusicFree.${MUSICFREE.aggregated}`} /> )} + { + console.log( + await DocumentPicker.getDocumentAsync({ + copyToCacheDirectory: false, + type: 'audio/*', + }) + ); + }} + title={'Local'} + /> ); }; diff --git a/yarn.lock b/yarn.lock index 23d39e76..4509b6fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7002,6 +7002,11 @@ expo-constants@~15.4.0: dependencies: "@expo/config" "~8.5.0" +expo-document-picker@~11.10.1: + version "11.10.1" + resolved "https://registry.yarnpkg.com/expo-document-picker/-/expo-document-picker-11.10.1.tgz#03394d77842a2fd7cb0a784a60098ee1ddd1012e" + integrity sha512-A1MiLfyXQ+KxanRO5lYxYQy3ryV+25JHe5Ai/BLV+FJU0QXByUF+Y/dn35WVPx5gpdZXC8UJ4ejg5SKSoeconw== + expo-file-system@~16.0.0: version "16.0.4" resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-16.0.4.tgz#f76b05e2224e705a30a75d50d650ee1dbb4dbaf7" From a4e71a08423b91d0f1a680dc0759b9d0b97b5f66 Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:36:36 -0800 Subject: [PATCH 02/11] feat: local playback --- .../com/noxplay/noxplayer/MainApplication.kt | 26 --------- .../noxplay/noxplayer/NoxAndroidAutoModule.kt | 58 +++++++++++++++++-- src/components/playlist/BiliSearch/Icons.tsx | 7 ++- .../playlist/BiliSearch/SearchMenu.tsx | 9 ++- src/utils/Utils.ts | 22 +++++++ 5 files changed, 88 insertions(+), 34 deletions(-) diff --git a/android/app/src/main/java/com/noxplay/noxplayer/MainApplication.kt b/android/app/src/main/java/com/noxplay/noxplayer/MainApplication.kt index e5775728..613e790b 100644 --- a/android/app/src/main/java/com/noxplay/noxplayer/MainApplication.kt +++ b/android/app/src/main/java/com/noxplay/noxplayer/MainApplication.kt @@ -2,9 +2,6 @@ package com.noxplay.noxplayer import android.app.Application import android.content.res.Configuration -import android.net.Uri -import android.provider.MediaStore -import android.util.Log import com.facebook.react.PackageList import com.facebook.react.ReactApplication import com.facebook.react.ReactNativeHost @@ -56,29 +53,6 @@ class MainApplication : Application(), ReactApplication { // If you opted-in for the New Architecture, we load the native entry point for this app. load() } - val uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3AMusic") - Log.d("RNTP", MediaStore.Audio.Media.getContentUri( - MediaStore.VOLUME_EXTERNAL - ).toString()) - Log.d("RNTP", MediaStore.Audio.Media.getContentUri( - MediaStore.VOLUME_INTERNAL - ).toString()) - val query = this.contentResolver.query( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - arrayOf( - MediaStore.Audio.Media._ID, - MediaStore.Audio.Media.RELATIVE_PATH, - MediaStore.Audio.Media.DISPLAY_NAME - ), null,null, null) - query?.use { cursor -> - Log.d("RNTP", cursor.count.toString()) - val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) - val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.RELATIVE_PATH) - val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME) - while (cursor.moveToNext()) { - Log.d("RNTP", cursor.getString(idColumn) + cursor.getString(pathColumn) + cursor.getString(nameColumn)) - } - } onApplicationCreate(this) } diff --git a/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt b/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt index 010ee960..fc0d02dd 100644 --- a/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt +++ b/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt @@ -2,28 +2,71 @@ package com.noxplay.noxplayer import android.app.ActivityManager import android.app.ApplicationExitInfo +import android.content.ContentUris import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build +import android.provider.MediaStore import android.provider.Settings +import android.util.Log import android.view.WindowManager -import androidx.annotation.RequiresApi +import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableNativeArray -class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { +class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(reactContext) { override fun getName() = "NoxAndroidAutoModule" + @ReactMethod fun listMediaDir(relativeDir: String, subdir: Boolean, callback: Promise) { + val results: WritableArray = WritableNativeArray() + try { + val query = reactApplicationContext.contentResolver.query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + arrayOf( + MediaStore.Audio.Media._ID, + MediaStore.Audio.Media.RELATIVE_PATH, + MediaStore.Audio.Media.DISPLAY_NAME + ), null,null, null) + query?.use { cursor -> + val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) + val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.RELATIVE_PATH) + val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME) + while (cursor.moveToNext()) { + val mediaPath = cursor.getString(pathColumn) + if (mediaPath == relativeDir || (subdir && mediaPath.startsWith(relativeDir))) { + + val mediaItem = Arguments.createMap() + mediaItem.putString("URI", + "content:/" + ContentUris.appendId( + Uri.Builder().path(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.path), + cursor.getLong(idColumn)).build().toString()) + mediaItem.putString("relativePath",mediaPath) + mediaItem.putString("fileName", cursor.getString(nameColumn)) + results.pushMap(mediaItem) + } + } + } + callback.resolve(results) + } catch (e: Exception) { + callback.resolve(results) + } + } + @ReactMethod fun getLastExitReason(callback: Promise) { try { val activity = reactApplicationContext.currentActivity val am = activity?.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val reason = am.getHistoricalProcessExitReasons("com.noxplay.noxplayer",0,0)[0].reason + val reason = am.getHistoricalProcessExitReasons( + "com.noxplay.noxplayer",0,0 + )[0].reason callback.resolve(reason in intArrayOf( ApplicationExitInfo.REASON_USER_REQUESTED, ApplicationExitInfo.REASON_USER_STOPPED, @@ -53,7 +96,10 @@ class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : ReactContext @ReactMethod fun askDrawOverAppsPermission() { val context = reactApplicationContext - val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:com.noxplay.noxplayer")) + val intent = Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:com.noxplay.noxplayer") + ) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(intent) } @@ -71,6 +117,8 @@ class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : ReactContext @ReactMethod fun isGestureNavigationMode(callback: Promise) { val context = reactApplicationContext - callback.resolve(Settings.Secure.getInt(context.contentResolver, "navigation_mode", 0) == 2) + callback.resolve( + Settings.Secure.getInt(context.contentResolver, "navigation_mode", 0) == 2 + ) } } diff --git a/src/components/playlist/BiliSearch/Icons.tsx b/src/components/playlist/BiliSearch/Icons.tsx index 3b08c579..43830322 100644 --- a/src/components/playlist/BiliSearch/Icons.tsx +++ b/src/components/playlist/BiliSearch/Icons.tsx @@ -27,9 +27,12 @@ const ICONS = { style={style.musicFreeIcon} /> ), - LOCAL: () => ( + LOCAL: (fill?: string) => ( - + ), }; diff --git a/src/components/playlist/BiliSearch/SearchMenu.tsx b/src/components/playlist/BiliSearch/SearchMenu.tsx index 3ef11cce..142a9edb 100644 --- a/src/components/playlist/BiliSearch/SearchMenu.tsx +++ b/src/components/playlist/BiliSearch/SearchMenu.tsx @@ -1,11 +1,15 @@ import * as React from 'react'; import { Menu } from 'react-native-paper'; import * as DocumentPicker from 'expo-document-picker'; +import { Platform, NativeModules } from 'react-native'; import { SEARCH_OPTIONS } from '@enums/Storage'; import { MUSICFREE } from '@utils/mediafetch/musicfree'; import ICONS from './Icons'; import { useNoxSetting } from '@stores/useApp'; +import { rgb2Hex } from '@utils/Utils'; + +const { NoxAndroidAutoModule } = NativeModules; interface Props { visible?: boolean; @@ -20,6 +24,7 @@ export default ({ menuCoords = { x: 0, y: 0 }, showMusicFree, }: Props) => { + const playerStyle = useNoxSetting(state => state.playerStyle); const setSearchOption = useNoxSetting(state => state.setSearchOption); const setDefaultSearch = (defaultSearch: SEARCH_OPTIONS | MUSICFREE) => { toggleVisible(); @@ -46,8 +51,10 @@ export default ({ /> )} ICONS.LOCAL(rgb2Hex(playerStyle.colors.primary))} onPress={async () => { + console.log(await NoxAndroidAutoModule.listMediaDir('Music/', true)); + return; console.log( await DocumentPicker.getDocumentAsync({ copyToCacheDirectory: false, diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 7fd30f02..ce0069a0 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -49,6 +49,28 @@ export const rgb2rgba = (rgb: string, a = 1) => { return `rgba(${extractedRGB[0][0]}, ${extractedRGB[1][0]}, ${extractedRGB[2][0]}, ${a})`; }; +const rgbToHex = (r: number, g: number, b: number) => + '#' + + [r, g, b] + .map(x => { + const hex = x.toString(16); + return hex.length === 1 ? '0' + hex : hex; + }) + .join(''); + +export const rgb2Hex = (rgb: string) => { + try { + const extractedRGB = [...rgb.matchAll(/(\d+)/g)]; + return rgbToHex( + Number(extractedRGB[0][0]), + Number(extractedRGB[1][0]), + Number(extractedRGB[2][0]) + ); + } catch { + return rgb; + } +}; + export const getUniqObjects = ( objects: Array, property: (object: T) => string From e0188d4c15c3e440a5d9db180aa599d8eda08336 Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Thu, 22 Feb 2024 10:46:14 -0800 Subject: [PATCH 03/11] feat: local playback --- src/components/playlist/BiliSearch/Icons.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/playlist/BiliSearch/Icons.tsx b/src/components/playlist/BiliSearch/Icons.tsx index 43830322..ff9be172 100644 --- a/src/components/playlist/BiliSearch/Icons.tsx +++ b/src/components/playlist/BiliSearch/Icons.tsx @@ -31,7 +31,7 @@ const ICONS = { ), From baf8c2880de8c2e9726474d2b9c9f253a40e9ac5 Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:03:49 -0800 Subject: [PATCH 04/11] feat: local playback --- .../playlist/BiliSearch/SearchMenu.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/playlist/BiliSearch/SearchMenu.tsx b/src/components/playlist/BiliSearch/SearchMenu.tsx index 142a9edb..54c39104 100644 --- a/src/components/playlist/BiliSearch/SearchMenu.tsx +++ b/src/components/playlist/BiliSearch/SearchMenu.tsx @@ -8,6 +8,7 @@ import { MUSICFREE } from '@utils/mediafetch/musicfree'; import ICONS from './Icons'; import { useNoxSetting } from '@stores/useApp'; import { rgb2Hex } from '@utils/Utils'; +import logger from '@utils/Logger'; const { NoxAndroidAutoModule } = NativeModules; @@ -53,14 +54,22 @@ export default ({ ICONS.LOCAL(rgb2Hex(playerStyle.colors.primary))} onPress={async () => { - console.log(await NoxAndroidAutoModule.listMediaDir('Music/', true)); - return; - console.log( + let selectedFile = ( await DocumentPicker.getDocumentAsync({ copyToCacheDirectory: false, type: 'audio/*', }) - ); + ).assets; + if (!selectedFile) return; + // TODO: he.decode? + let uri = selectedFile[0].uri; + logger.debug(`[DocumentPicker] selected uri: ${uri}`); + // content://com.android.externalstorage.documents/document/primary%3AMusic%2FTttt%2FGggg.mp3 + let parsedURI = uri + .substring(uri.indexOf('%3A') + 3, uri.lastIndexOf('%2F')) + .replaceAll('%2F', '/'); + console.log(await NoxAndroidAutoModule.listMediaDir(parsedURI, true)); + return; }} title={'Local'} /> From c6d15ef6a271cf591222d90001a8994a0a267af0 Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:02:26 -0800 Subject: [PATCH 05/11] feat: local playback --- .../java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt | 7 +++++-- src/components/playlist/BiliSearch/SearchMenu.tsx | 7 ++++++- src/utils/ffmpeg/ffmpeg.ts | 8 +++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt b/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt index fc0d02dd..50afbddf 100644 --- a/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt +++ b/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt @@ -32,16 +32,17 @@ class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : arrayOf( MediaStore.Audio.Media._ID, MediaStore.Audio.Media.RELATIVE_PATH, - MediaStore.Audio.Media.DISPLAY_NAME + MediaStore.Audio.Media.DISPLAY_NAME, + MediaStore.Audio.Media.DATA ), null,null, null) query?.use { cursor -> val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.RELATIVE_PATH) val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME) + val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA) while (cursor.moveToNext()) { val mediaPath = cursor.getString(pathColumn) if (mediaPath == relativeDir || (subdir && mediaPath.startsWith(relativeDir))) { - val mediaItem = Arguments.createMap() mediaItem.putString("URI", "content:/" + ContentUris.appendId( @@ -49,12 +50,14 @@ class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : cursor.getLong(idColumn)).build().toString()) mediaItem.putString("relativePath",mediaPath) mediaItem.putString("fileName", cursor.getString(nameColumn)) + mediaItem.putString("realPath", cursor.getString(dataColumn)) results.pushMap(mediaItem) } } } callback.resolve(results) } catch (e: Exception) { + Log.e("NoxFileUtil", e.toString()) callback.resolve(results) } } diff --git a/src/components/playlist/BiliSearch/SearchMenu.tsx b/src/components/playlist/BiliSearch/SearchMenu.tsx index 54c39104..28601f02 100644 --- a/src/components/playlist/BiliSearch/SearchMenu.tsx +++ b/src/components/playlist/BiliSearch/SearchMenu.tsx @@ -9,6 +9,7 @@ import ICONS from './Icons'; import { useNoxSetting } from '@stores/useApp'; import { rgb2Hex } from '@utils/Utils'; import logger from '@utils/Logger'; +import { probeMetadata } from '@utils/ffmpeg/ffmpeg'; const { NoxAndroidAutoModule } = NativeModules; @@ -68,7 +69,11 @@ export default ({ let parsedURI = uri .substring(uri.indexOf('%3A') + 3, uri.lastIndexOf('%2F')) .replaceAll('%2F', '/'); - console.log(await NoxAndroidAutoModule.listMediaDir(parsedURI, true)); + let mediaFiles = await NoxAndroidAutoModule.listMediaDir( + parsedURI, + true + ); + mediaFiles.forEach((v: any) => probeMetadata(v.realPath)); return; }} title={'Local'} diff --git a/src/utils/ffmpeg/ffmpeg.ts b/src/utils/ffmpeg/ffmpeg.ts index d03698e7..418225a9 100644 --- a/src/utils/ffmpeg/ffmpeg.ts +++ b/src/utils/ffmpeg/ffmpeg.ts @@ -1,10 +1,16 @@ -import { FFmpegKit } from 'ffmpeg-kit-react-native'; +import { FFmpegKit, FFprobeKit } from 'ffmpeg-kit-react-native'; import RNFetchBlob from 'react-native-blob-util'; import TrackPlayer from 'react-native-track-player'; import { logger } from '../Logger'; import { r128gain2Volume } from '../Utils'; +export const probeMetadata = async (fspath: string) => { + const session = await FFprobeKit.execute( + `-show_format -print_format json '${fspath}'` + ); + console.log(await session.getOutput()); +}; const parseReplayGainLog = (log: string) => { const regex = /Parsed_replaygain.+ track_gain = (.+) dB/g; regex.exec(log); From 02b4337e2b47071da6e38acd47ecc1d42dde0f69 Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Fri, 23 Feb 2024 06:14:19 -0800 Subject: [PATCH 06/11] feat: local playback --- .../com/noxplay/noxplayer/NoxAndroidAutoModule.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt b/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt index 50afbddf..e2504efa 100644 --- a/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt +++ b/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt @@ -24,7 +24,7 @@ class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { override fun getName() = "NoxAndroidAutoModule" - @ReactMethod fun listMediaDir(relativeDir: String, subdir: Boolean, callback: Promise) { + private fun _listMediaDir(relativeDir: String, subdir: Boolean, selection: String? = null): WritableArray { val results: WritableArray = WritableNativeArray() try { val query = reactApplicationContext.contentResolver.query( @@ -34,7 +34,7 @@ class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : MediaStore.Audio.Media.RELATIVE_PATH, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.DATA - ), null,null, null) + ), selection,null, null) query?.use { cursor -> val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.RELATIVE_PATH) @@ -55,11 +55,18 @@ class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : } } } - callback.resolve(results) } catch (e: Exception) { Log.e("NoxFileUtil", e.toString()) - callback.resolve(results) } + return results + } + + @ReactMethod fun listMediaDir(relativeDir: String, subdir: Boolean, callback: Promise) { + callback.resolve(_listMediaDir(relativeDir, subdir)) + } + + @ReactMethod fun listMediaFile(relativeDir: String, subdir: Boolean, filename: String, callback: Promise) { + callback.resolve(_listMediaDir(relativeDir, subdir, "${MediaStore.Audio.Media.DISPLAY_NAME} = $filename")) } @ReactMethod fun getLastExitReason(callback: Promise) { From eda66ec09e750c7dd38992642d46d099519e6fbf Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:44:13 -0800 Subject: [PATCH 07/11] feat: local playback --- .../noxplay/noxplayer/NoxAndroidAutoModule.kt | 15 ++-- .../playlist/BiliSearch/BiliSearchbar.tsx | 1 + .../playlist/BiliSearch/SearchMenu.tsx | 40 +++++------ src/enums/MediaFetch.ts | 1 + src/utils/BiliSearch.ts | 5 ++ src/utils/ffmpeg/ffmpeg.ts | 7 +- src/utils/mediafetch/local.ts | 69 +++++++++++++++++++ src/utils/mediafetch/resolveURL.ts | 2 + 8 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 src/utils/mediafetch/local.ts diff --git a/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt b/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt index e2504efa..80b0135f 100644 --- a/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt +++ b/android/app/src/main/java/com/noxplay/noxplayer/NoxAndroidAutoModule.kt @@ -36,9 +36,9 @@ class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : MediaStore.Audio.Media.DATA ), selection,null, null) query?.use { cursor -> - val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) - val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.RELATIVE_PATH) - val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME) + val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID) + val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.RELATIVE_PATH) + val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME) val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA) while (cursor.moveToNext()) { val mediaPath = cursor.getString(pathColumn) @@ -65,10 +65,15 @@ class NoxAndroidAutoModule(reactContext: ReactApplicationContext) : callback.resolve(_listMediaDir(relativeDir, subdir)) } - @ReactMethod fun listMediaFile(relativeDir: String, subdir: Boolean, filename: String, callback: Promise) { - callback.resolve(_listMediaDir(relativeDir, subdir, "${MediaStore.Audio.Media.DISPLAY_NAME} = $filename")) + @ReactMethod fun listMediaFileByFName(filename: String, callback: Promise) { + callback.resolve(_listMediaDir("", true, + "${MediaStore.Audio.Media.DISPLAY_NAME} = $filename")) } + @ReactMethod fun listMediaFileByID(id: String, callback: Promise) { + callback.resolve(_listMediaDir("", true, + "${MediaStore.Audio.Media._ID} = $id")) + } @ReactMethod fun getLastExitReason(callback: Promise) { try { val activity = reactApplicationContext.currentActivity diff --git a/src/components/playlist/BiliSearch/BiliSearchbar.tsx b/src/components/playlist/BiliSearch/BiliSearchbar.tsx index 5ea54657..2a79f3ce 100644 --- a/src/components/playlist/BiliSearch/BiliSearchbar.tsx +++ b/src/components/playlist/BiliSearch/BiliSearchbar.tsx @@ -137,6 +137,7 @@ export default ({ toggleVisible={toggleVisible} menuCoords={menuCoords} showMusicFree={showMusicFree} + setSearchVal={setSearchVal} /> void; menuCoords?: NoxTheme.coordinates; showMusicFree?: boolean; + setSearchVal: (v: string) => void; } export default ({ @@ -25,6 +26,7 @@ export default ({ toggleVisible = () => undefined, menuCoords = { x: 0, y: 0 }, showMusicFree, + setSearchVal, }: Props) => { const playerStyle = useNoxSetting(state => state.playerStyle); const setSearchOption = useNoxSetting(state => state.setSearchOption); @@ -32,6 +34,21 @@ export default ({ toggleVisible(); setSearchOption(defaultSearch); }; + const chooseLocalFolder = async () => { + let selectedFile = ( + await DocumentPicker.getDocumentAsync({ + copyToCacheDirectory: false, + type: 'audio/*', + }) + ).assets; + if (!selectedFile) return; + const uri = selectedFile[0].uri; + let mediaFiles = await NoxAndroidAutoModule.listMediaFileByID( + uri.substring(uri.lastIndexOf('%3A') + 3) + ); + setSearchVal(`local://${mediaFiles[0].relativePath}`); + toggleVisible(); + }; return ( @@ -54,28 +71,7 @@ export default ({ )} ICONS.LOCAL(rgb2Hex(playerStyle.colors.primary))} - onPress={async () => { - let selectedFile = ( - await DocumentPicker.getDocumentAsync({ - copyToCacheDirectory: false, - type: 'audio/*', - }) - ).assets; - if (!selectedFile) return; - // TODO: he.decode? - let uri = selectedFile[0].uri; - logger.debug(`[DocumentPicker] selected uri: ${uri}`); - // content://com.android.externalstorage.documents/document/primary%3AMusic%2FTttt%2FGggg.mp3 - let parsedURI = uri - .substring(uri.indexOf('%3A') + 3, uri.lastIndexOf('%2F')) - .replaceAll('%2F', '/'); - let mediaFiles = await NoxAndroidAutoModule.listMediaDir( - parsedURI, - true - ); - mediaFiles.forEach((v: any) => probeMetadata(v.realPath)); - return; - }} + onPress={chooseLocalFolder} title={'Local'} /> diff --git a/src/enums/MediaFetch.ts b/src/enums/MediaFetch.ts index d71fb821..6659b3c9 100644 --- a/src/enums/MediaFetch.ts +++ b/src/enums/MediaFetch.ts @@ -4,4 +4,5 @@ export enum SOURCE { steriatk = 'steriatk', ytbvideo = 'ytbvideo', biliBangumi = 'biliBangumi', + local = 'local', } diff --git a/src/utils/BiliSearch.ts b/src/utils/BiliSearch.ts index e5bdd1c6..ffab1cb7 100644 --- a/src/utils/BiliSearch.ts +++ b/src/utils/BiliSearch.ts @@ -20,6 +20,7 @@ import ytbmixlistFetch from './mediafetch/ytbmixlist'; import ytbsearchFetch from './mediafetch/ytbsearch'; import bililiveFetch from './mediafetch/bililive'; import bilisubliveFetch from './mediafetch/bilisublive'; +import localFetch from '@utils/mediafetch/local'; import { regexFetchProps } from './mediafetch/generic'; import { MUSICFREE, searcher } from './mediafetch/musicfree'; import { getMusicFreePlugin } from '@utils/ChromeStorage'; @@ -116,6 +117,10 @@ interface ReExtraction { } const reExtractions: ReExtraction[] = [ + { + match: localFetch.regexSearchMatch, + fetch: localFetch.regexFetch, + }, { match: biliBangumiFetch.regexSearchMatch, fetch: biliBangumiFetch.regexFetch, diff --git a/src/utils/ffmpeg/ffmpeg.ts b/src/utils/ffmpeg/ffmpeg.ts index 418225a9..1026140f 100644 --- a/src/utils/ffmpeg/ffmpeg.ts +++ b/src/utils/ffmpeg/ffmpeg.ts @@ -7,10 +7,13 @@ import { r128gain2Volume } from '../Utils'; export const probeMetadata = async (fspath: string) => { const session = await FFprobeKit.execute( - `-show_format -print_format json '${fspath}'` + `-v quiet -print_format json -show_format '${fspath}'` ); - console.log(await session.getOutput()); + const parsedMetadata = JSON.parse(await session.getOutput()); + logger.debug(parsedMetadata); + return parsedMetadata.format; }; + const parseReplayGainLog = (log: string) => { const regex = /Parsed_replaygain.+ track_gain = (.+) dB/g; regex.exec(log); diff --git a/src/utils/mediafetch/local.ts b/src/utils/mediafetch/local.ts new file mode 100644 index 00000000..bd5a0845 --- /dev/null +++ b/src/utils/mediafetch/local.ts @@ -0,0 +1,69 @@ +/** + * refactor: + * bilisearch workflow: + * reExtractSearch matches regex patterns and use the corresponding fetch functions; + * fetch function takes extracted and calls a dataProcess.js fetch function; + * dataprocess fetch function fetches VIDEOINFO using data.js fetch function, then parses into SONGS + * data.js fetch function fetches VIDEOINFO. + * steps to refactor: + * each site needs a fetch to parse regex extracted, a videoinfo fetcher and a song fetcher. + */ +import { Platform, NativeModules } from 'react-native'; + +import { probeMetadata } from '@utils/ffmpeg/ffmpeg'; +import { SOURCE } from '@enums/MediaFetch'; +import { regexFetchProps } from './generic'; +import SongTS from '@objects/Song'; + +const { NoxAndroidAutoModule } = NativeModules; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const songFetch = async ( + fpath: string, + favlist: string[] +): Promise => { + const mediaFiles = await NoxAndroidAutoModule.listMediaDir(fpath, true); + return Promise.all( + mediaFiles + .filter((v: any) => !favlist.includes(v.realPath)) + .map(async (v: any) => { + const probedMetadata = await probeMetadata(v.realPath); + return SongTS({ + cid: `${SOURCE.local}-${v.realPath}`, + bvid: `file://${v.realPath}`, + name: probedMetadata.tags?.title || v.fileName, + nameRaw: probedMetadata.tags?.title || v.fileName, + singer: probedMetadata.tags?.artist || '', + singerId: probedMetadata.tags?.artist || '', + cover: + 'https://i2.hdslb.com/bfs/face/b70f6e62e4582d4fa5d48d86047e64eb57d7504e.jpg', + lyric: '', + page: 0, + duration: Number(probedMetadata.duration) || 0, + album: probedMetadata.tags?.album || '', + source: SOURCE.local, + }); + }) + ); +}; + +const regexFetch = async ({ + reExtracted, + favList = [], +}: regexFetchProps): Promise => ({ + songList: await songFetch(reExtracted[1]!, favList), +}); + +const resolveURL = async (song: NoxMedia.Song) => { + return { url: song.bvid }; +}; + +const refreshSong = (song: NoxMedia.Song) => song; + +export default { + regexSearchMatch: /local:\/\/(.+)/, + regexFetch, + regexResolveURLMatch: /^local-/, + resolveURL, + refreshSong, +}; diff --git a/src/utils/mediafetch/resolveURL.ts b/src/utils/mediafetch/resolveURL.ts index 141487b0..21c07d5b 100644 --- a/src/utils/mediafetch/resolveURL.ts +++ b/src/utils/mediafetch/resolveURL.ts @@ -3,6 +3,7 @@ import biliaudioFetch from './biliaudio'; import ytbvideoFetch from '@utils/mediafetch/ytbvideo'; import bililiveFetch from './bililive'; import biliBangumiFetch from './biliBangumi'; +import localFetch from '@utils/mediafetch/local'; import { logger } from '../Logger'; import { regexMatchOperations } from '../Utils'; import { resolver, MUSICFREE } from './musicfree'; @@ -37,6 +38,7 @@ export const fetchPlayUrlPromise = async ( [ytbvideoFetch.regexResolveURLMatch, ytbvideoFetch.resolveURL], [bililiveFetch.regexResolveURLMatch, bililiveFetch.resolveURL], [biliBangumiFetch.regexResolveURLMatch, biliBangumiFetch.resolveURL], + [localFetch.regexResolveURLMatch, localFetch.resolveURL], ]; const regexResolveURLsWrapped: regResolve = regexResolveURLs.map(entry => [ entry[0], From 8fd53729eaa2d7a06bec03174a3400fa3dd3e20b Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:20:04 -0800 Subject: [PATCH 08/11] feat: local playback --- src/stores/appStore.ts | 10 +++++++--- src/utils/SongOperations.ts | 1 + src/utils/ffmpeg/ffmpeg.ts | 8 ++++++++ src/utils/mediafetch/local.ts | 9 ++++----- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/stores/appStore.ts b/src/stores/appStore.ts index 7c839155..3a6a1e21 100644 --- a/src/stores/appStore.ts +++ b/src/stores/appStore.ts @@ -11,6 +11,7 @@ import { import { logger } from '@utils/Logger'; import rejson from '../utils/rejson.json'; import { LoadJSONRegExtractors } from '../utils/re'; +import { SOURCE } from '@enums/MediaFetch'; interface AppStore { pipMode: boolean; @@ -180,9 +181,12 @@ export const cacheResolvedURL = async ( ) { logger.debug(`[CacheResolveURL] ${song.parsedName} needs to be refetched.`); const result = await resolveURL(song); - appStore.setState({ - cachedResolveURLMap: { ...cachedResolveURLMap, [song.id]: result }, - }); + // HACK: do not cache any local source files + if (song.source !== SOURCE.local) { + appStore.setState({ + cachedResolveURLMap: { ...cachedResolveURLMap, [song.id]: result }, + }); + } return result; } return cachedResolvedURL; diff --git a/src/utils/SongOperations.ts b/src/utils/SongOperations.ts index 21274c27..2055a331 100644 --- a/src/utils/SongOperations.ts +++ b/src/utils/SongOperations.ts @@ -60,6 +60,7 @@ export const resolveUrl = async (song: NoxMedia.Song, iOS = true) => { logger.debug( `[SongResolveURL] cache ${cachedUrl ? 'found' : 'missed'}, ${song.id}` ); + const cacheWrapper = async ( song: NoxMedia.Song ): Promise => { diff --git a/src/utils/ffmpeg/ffmpeg.ts b/src/utils/ffmpeg/ffmpeg.ts index 1026140f..cbaf675b 100644 --- a/src/utils/ffmpeg/ffmpeg.ts +++ b/src/utils/ffmpeg/ffmpeg.ts @@ -5,6 +5,14 @@ import TrackPlayer from 'react-native-track-player'; import { logger } from '../Logger'; import { r128gain2Volume } from '../Utils'; +export const cacheAlbumArt = async (fpath: string) => { + // HACK: exoplayer handles embedded art but I also need this for the UI... + await FFmpegKit.execute( + `-i '${fpath}' -an -vcodec copy ${RNFetchBlob.fs.dirs.CacheDir}/tempCover.jpg` + ); + return `${RNFetchBlob.fs.dirs.CacheDir}/tempCover.jpg`; +}; + export const probeMetadata = async (fspath: string) => { const session = await FFprobeKit.execute( `-v quiet -print_format json -show_format '${fspath}'` diff --git a/src/utils/mediafetch/local.ts b/src/utils/mediafetch/local.ts index bd5a0845..fc5a283f 100644 --- a/src/utils/mediafetch/local.ts +++ b/src/utils/mediafetch/local.ts @@ -8,9 +8,9 @@ * steps to refactor: * each site needs a fetch to parse regex extracted, a videoinfo fetcher and a song fetcher. */ -import { Platform, NativeModules } from 'react-native'; +import { NativeModules } from 'react-native'; -import { probeMetadata } from '@utils/ffmpeg/ffmpeg'; +import { probeMetadata, cacheAlbumArt } from '@utils/ffmpeg/ffmpeg'; import { SOURCE } from '@enums/MediaFetch'; import { regexFetchProps } from './generic'; import SongTS from '@objects/Song'; @@ -35,8 +35,7 @@ const songFetch = async ( nameRaw: probedMetadata.tags?.title || v.fileName, singer: probedMetadata.tags?.artist || '', singerId: probedMetadata.tags?.artist || '', - cover: - 'https://i2.hdslb.com/bfs/face/b70f6e62e4582d4fa5d48d86047e64eb57d7504e.jpg', + cover: '', lyric: '', page: 0, duration: Number(probedMetadata.duration) || 0, @@ -55,7 +54,7 @@ const regexFetch = async ({ }); const resolveURL = async (song: NoxMedia.Song) => { - return { url: song.bvid }; + return { url: song.bvid, cover: await cacheAlbumArt(song.bvid) }; }; const refreshSong = (song: NoxMedia.Song) => song; From 65b59faa53a26425c5bd5bd0d41a745065b037e0 Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:47:07 -0800 Subject: [PATCH 09/11] feat: local playback --- .../playlist/BiliSearch/SearchMenu.tsx | 16 +++++++++------- src/localization/en/translation.json | 3 +++ src/localization/zhcn/translation.json | 3 +++ src/utils/mediafetch/local.ts | 3 ++- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/components/playlist/BiliSearch/SearchMenu.tsx b/src/components/playlist/BiliSearch/SearchMenu.tsx index 62c4a129..82c3a213 100644 --- a/src/components/playlist/BiliSearch/SearchMenu.tsx +++ b/src/components/playlist/BiliSearch/SearchMenu.tsx @@ -2,14 +2,13 @@ import * as React from 'react'; import { Menu } from 'react-native-paper'; import * as DocumentPicker from 'expo-document-picker'; import { Platform, NativeModules } from 'react-native'; +import { useTranslation } from 'react-i18next'; import { SEARCH_OPTIONS } from '@enums/Storage'; import { MUSICFREE } from '@utils/mediafetch/musicfree'; import ICONS from './Icons'; import { useNoxSetting } from '@stores/useApp'; import { rgb2Hex } from '@utils/Utils'; -import logger from '@utils/Logger'; -import { probeMetadata } from '@utils/ffmpeg/ffmpeg'; const { NoxAndroidAutoModule } = NativeModules; @@ -28,6 +27,7 @@ export default ({ showMusicFree, setSearchVal, }: Props) => { + const { t } = useTranslation(); const playerStyle = useNoxSetting(state => state.playerStyle); const setSearchOption = useNoxSetting(state => state.setSearchOption); const setDefaultSearch = (defaultSearch: SEARCH_OPTIONS | MUSICFREE) => { @@ -69,11 +69,13 @@ export default ({ title={`MusicFree.${MUSICFREE.aggregated}`} /> )} - ICONS.LOCAL(rgb2Hex(playerStyle.colors.primary))} - onPress={chooseLocalFolder} - title={'Local'} - /> + {Platform.OS === 'android' && ( + ICONS.LOCAL(rgb2Hex(playerStyle.colors.primary))} + onPress={chooseLocalFolder} + title={t('Menu.local')} + /> + )} ); }; diff --git a/src/localization/en/translation.json b/src/localization/en/translation.json index 2f44233a..fbbb2df1 100644 --- a/src/localization/en/translation.json +++ b/src/localization/en/translation.json @@ -284,5 +284,8 @@ }, "Accessibility": { "gif": "GIF" + }, + "Menu": { + "local": "Local" } } diff --git a/src/localization/zhcn/translation.json b/src/localization/zhcn/translation.json index 8aa033a6..44bcf9fe 100644 --- a/src/localization/zhcn/translation.json +++ b/src/localization/zhcn/translation.json @@ -253,5 +253,8 @@ "artistMatch": "搜索歌手", "albumMatch": "搜索专辑", "cachedMatch": "已缓存" + }, + "Menu": { + "local": "本di" } } diff --git a/src/utils/mediafetch/local.ts b/src/utils/mediafetch/local.ts index fc5a283f..51fc9281 100644 --- a/src/utils/mediafetch/local.ts +++ b/src/utils/mediafetch/local.ts @@ -8,7 +8,7 @@ * steps to refactor: * each site needs a fetch to parse regex extracted, a videoinfo fetcher and a song fetcher. */ -import { NativeModules } from 'react-native'; +import { Platform, NativeModules } from 'react-native'; import { probeMetadata, cacheAlbumArt } from '@utils/ffmpeg/ffmpeg'; import { SOURCE } from '@enums/MediaFetch'; @@ -22,6 +22,7 @@ const songFetch = async ( fpath: string, favlist: string[] ): Promise => { + if (Platform.OS !== 'android') return []; const mediaFiles = await NoxAndroidAutoModule.listMediaDir(fpath, true); return Promise.all( mediaFiles From de4c7e4882294a6a59c0754a2c139e8ed6fd695d Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:56:02 -0800 Subject: [PATCH 10/11] feat: local playback --- src/components/player/TrackInfo/AlbumArt.tsx | 1 + src/stores/appStore.ts | 10 +++------- src/utils/mediafetch/local.ts | 5 ++++- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/player/TrackInfo/AlbumArt.tsx b/src/components/player/TrackInfo/AlbumArt.tsx index 40c9564e..438d8a86 100644 --- a/src/components/player/TrackInfo/AlbumArt.tsx +++ b/src/components/player/TrackInfo/AlbumArt.tsx @@ -46,6 +46,7 @@ const AlbumArt: React.FC = ({ useNativeDriver: true, }), ]).start(() => { + console.log('artwork', track?.artwork); console.log('TrackInfo: Setting imagevisible to Image', !isImageVisible); setIsImageVisible(false); }); diff --git a/src/stores/appStore.ts b/src/stores/appStore.ts index 3a6a1e21..7c839155 100644 --- a/src/stores/appStore.ts +++ b/src/stores/appStore.ts @@ -11,7 +11,6 @@ import { import { logger } from '@utils/Logger'; import rejson from '../utils/rejson.json'; import { LoadJSONRegExtractors } from '../utils/re'; -import { SOURCE } from '@enums/MediaFetch'; interface AppStore { pipMode: boolean; @@ -181,12 +180,9 @@ export const cacheResolvedURL = async ( ) { logger.debug(`[CacheResolveURL] ${song.parsedName} needs to be refetched.`); const result = await resolveURL(song); - // HACK: do not cache any local source files - if (song.source !== SOURCE.local) { - appStore.setState({ - cachedResolveURLMap: { ...cachedResolveURLMap, [song.id]: result }, - }); - } + appStore.setState({ + cachedResolveURLMap: { ...cachedResolveURLMap, [song.id]: result }, + }); return result; } return cachedResolvedURL; diff --git a/src/utils/mediafetch/local.ts b/src/utils/mediafetch/local.ts index 51fc9281..bd342ba7 100644 --- a/src/utils/mediafetch/local.ts +++ b/src/utils/mediafetch/local.ts @@ -9,6 +9,7 @@ * each site needs a fetch to parse regex extracted, a videoinfo fetcher and a song fetcher. */ import { Platform, NativeModules } from 'react-native'; +import RNFetchBlob from 'react-native-blob-util'; import { probeMetadata, cacheAlbumArt } from '@utils/ffmpeg/ffmpeg'; import { SOURCE } from '@enums/MediaFetch'; @@ -55,7 +56,9 @@ const regexFetch = async ({ }); const resolveURL = async (song: NoxMedia.Song) => { - return { url: song.bvid, cover: await cacheAlbumArt(song.bvid) }; + const artworkURI = await cacheAlbumArt(song.bvid); + const artworkBase64 = await RNFetchBlob.fs.readFile(artworkURI, 'base64'); + return { url: song.bvid, cover: `data:image/png;base64,${artworkBase64}` }; }; const refreshSong = (song: NoxMedia.Song) => song; From 2a1eeafe3b2e71b7b8fbc17f103348fbd6af8b22 Mon Sep 17 00:00:00 2001 From: lovegaoshi <106490582+lovegaoshi@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:56:32 -0800 Subject: [PATCH 11/11] feat: local playback --- src/components/player/TrackInfo/AlbumArt.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/player/TrackInfo/AlbumArt.tsx b/src/components/player/TrackInfo/AlbumArt.tsx index 438d8a86..40c9564e 100644 --- a/src/components/player/TrackInfo/AlbumArt.tsx +++ b/src/components/player/TrackInfo/AlbumArt.tsx @@ -46,7 +46,6 @@ const AlbumArt: React.FC = ({ useNativeDriver: true, }), ]).start(() => { - console.log('artwork', track?.artwork); console.log('TrackInfo: Setting imagevisible to Image', !isImageVisible); setIsImageVisible(false); });