关于react native文件路径的烦心事
前言:我听歌比较喜欢播放本地音乐,但是那些本地播放APP总会有些这样或那样让我不顺的问题,比如中文/日文识别为乱码,比如换一个文件夹它就不知道我上一次在这文件夹里播的啥音乐,再比如无法按文件名排序。
最近公司项目做APP,学了react native。于是想用react native把本地音乐播放器给造一个出来了,本来一天就能搞定的事,前前后后磨了快一星期,终于搞定了。
1.由于我之前用荣耀10做测试,后来换成小米13,直接把react-native-document-picker的多选给搞废了,组件不兼容多选必须得用安卓原生来,然后开始研究原生代码该怎么写……
2.react-native-track-player没有是否已经初始化的判定,导致我修改完代码每次保存后,都还再次运行useEffect提示说已经注册了插件,最后还好用useRef的current是否存在来解决。
3.小米底部安全区域遮挡问题还困扰了我一下,最后在MainActivity.java重写onCreate + styles.xml增加navigationBarColor配置下搞定。
4.最大的障碍!!!react-native-track-player究竟能播放哪种路径的文件?
由于我用的react-native-document-picker选择文件,它默认返回的【content://com.android.externalstorage.documents/document/primary%3AMusic%2FEnglish%2FXXX.mp3】是不能直接播放的。
然后我找到了它的API文档,有个copyTo的配置,然后返回的【file:///data/user/0/com.caicemusic/files/d99163f2-aa31-45d3-a4a7-947ecf13d18e/XXX.mp3】是可以播的,但是!这存在两个问题,一是我选择的音乐至少都上百首,全部copy后,APP的存储体积飙升几个G。二是,我的小米13由于上述的1.无法多选导致这路被封死了。
所以我的研究方向就变成了,怎么把content:\\文件转化为file:\\,就是这问题卡了我一周!
什么react-native-fs、rn-fetch-blob等被我装装卸卸了好多次,后来用原生java转,在DocumentFile.fromTreeUri(getReactApplicationContext(), uri)拿到文件后,通过file.getUri().toString()确实也拿到了缓存路径可以播,但【content://com.android.externalstorage.documents/tree/primary%3AMusic%2FEnglish/document/primary%3AMusic%2FEnglish%2FXXX.mp3】的路径在退出APP之后再进来,就无法再次使用了。
之后我才想到,可以参考那些已有项目,看看别人是怎么写音乐播放器,于是找到了,它直接用react-native-get-music-files来获取文件路径【/storage/emulated/0/Music/English/XXX.mp3】,这个路径好眼熟,我曾经用rn-fetch-blob拿到过呀,原来它就能播?
最终,这个符合我个人需求的音乐播放器总算是做完了。
后续:【content://com.android.externalstorage.documents/tree/primary%3AMusic%2FEnglish/document/primary%3AMusic%2FEnglish%2FXXX.mp3】现在这种路径的音乐也可以在退出APP再进入的时候播放了,通过原生代码就不会出现这种问题。
package com.caicemusic.app;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.util.Log;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.BaseActivityEventListener;
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 org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class FilePathModule extends ReactContextBaseJavaModule {
private Promise folderPromise;
public FilePathModule(@NonNull ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addActivityEventListener(activityEventListener);
}
@NonNull
@Override
public String getName() {
return "FilePathModule";
}
/**
* 打开文件夹选择器
*/
@ReactMethod
public void pickFolder(Promise promise) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject("ENOACT", "Activity 不存在");
return;
}
folderPromise = promise;
try {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
currentActivity.startActivityForResult(intent, 12345);
} catch (Exception e) {
promise.reject("ERROR", e.getMessage());
}
}
@ReactMethod
public void listFilesInFolder(String folderUriStr, Promise promise) {
try {
Uri folderUri = Uri.parse(folderUriStr);
ContentResolver resolver = getReactApplicationContext().getContentResolver();
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
folderUri, DocumentsContract.getTreeDocumentId(folderUri));
Cursor cursor = resolver.query(childrenUri,
new String[]{
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_MIME_TYPE
},
null, null, null);
if (cursor == null) {
promise.resolve("[]");
return;
}
List<JSONObject> fileList = new ArrayList<>();
while (cursor.moveToNext()) {
String name = cursor.getString(0);
String docId = cursor.getString(1);
String mime = cursor.getString(2);
if (!DocumentsContract.Document.MIME_TYPE_DIR.equals(mime)) {
Uri fileUri = DocumentsContract.buildDocumentUriUsingTree(folderUri, docId);
JSONObject fileObj = new JSONObject();
fileObj.put("name", name);
fileObj.put("uri", fileUri.toString());
fileList.add(fileObj);
}
}
cursor.close();
// 按文件名正序排序
Collections.sort(fileList, new Comparator<JSONObject>() {
@Override
public int compare(JSONObject o1, JSONObject o2) {
try {
return o1.getString("name").compareToIgnoreCase(o2.getString("name"));
} catch (JSONException e) {
return 0;
}
}
});
// 转回 JSONArray
JSONArray files = new JSONArray();
for (JSONObject obj : fileList) {
files.put(obj);
}
promise.resolve(files.toString());
} catch (Exception e) {
Log.e("FilePathModule", "遍历文件失败", e);
promise.reject("ERROR", e.getMessage());
}
}
private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
if (requestCode == 12345 && folderPromise != null) {
if (resultCode == Activity.RESULT_OK && data != null) {
Uri uri = data.getData();
if (uri != null) {
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
activity.getContentResolver().takePersistableUriPermission(uri, takeFlags);
folderPromise.resolve(uri.toString());
} else {
folderPromise.reject("ENOURI", "未选择文件夹");
}
} else {
folderPromise.reject("CANCELLED", "用户取消选择");
}
folderPromise = null;
}
}
};
}
更多推荐


所有评论(0)