移动壳放宽音频选择器候选
移动端音频导入的 DocumentPicker 候选加入 audio/* 补充泛 MIME 仍按扩展名和 bytes 校验的移动壳测试 移动壳配置门禁锁定音频选择器类型和 bytes 校验 同步宿主壳方案文档和共享决策日志
This commit is contained in:
@@ -67,6 +67,9 @@ const WAV_BASE64 = Buffer.from('RIFF\x04\x00\x00\x00WAVE', 'binary').toString(
|
||||
const MP3_BASE64 = Buffer.from('ID3\x04\x00\x00\x00\x00\x00\x10', 'binary').toString(
|
||||
'base64',
|
||||
);
|
||||
const MP4_AUDIO_BASE64 = encodeBytes([
|
||||
0, 0, 0, 0x18, 0x66, 0x74, 0x79, 0x70, 0x4d, 0x34, 0x41, 0x20,
|
||||
]);
|
||||
const WEBM_BASE64 = encodeBytes([0x1a, 0x45, 0xdf, 0xa3, 0x01, 0x00]);
|
||||
|
||||
vi.mock('expo-clipboard', () => ({
|
||||
@@ -1370,6 +1373,7 @@ describe('handleMobileHostBridgeMessage', () => {
|
||||
copyToCacheDirectory: true,
|
||||
multiple: false,
|
||||
type: [
|
||||
'audio/*',
|
||||
'audio/mpeg',
|
||||
'audio/mp4',
|
||||
'audio/wav',
|
||||
@@ -1379,6 +1383,52 @@ describe('handleMobileHostBridgeMessage', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('file.importAudio 允许系统 MIME 缺失时按扩展名和 bytes 校验导入', async () => {
|
||||
fileBase64Data.set('file:///private/mobile/hit.m4a', MP3_BASE64);
|
||||
vi.mocked(DocumentPicker.getDocumentAsync).mockResolvedValue({
|
||||
canceled: false,
|
||||
assets: [
|
||||
{
|
||||
uri: 'file:///private/mobile/hit.m4a',
|
||||
name: 'hit.m4a',
|
||||
mimeType: 'application/octet-stream',
|
||||
size: 10,
|
||||
lastModified: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const mismatched = await send(request('file.importAudio'));
|
||||
|
||||
expect(expectFailed(mismatched).error.message).toBe(
|
||||
'audio bytes do not match MIME',
|
||||
);
|
||||
|
||||
fileBase64Data.set('file:///private/mobile/hit.m4a', MP4_AUDIO_BASE64);
|
||||
vi.mocked(DocumentPicker.getDocumentAsync).mockResolvedValue({
|
||||
canceled: false,
|
||||
assets: [
|
||||
{
|
||||
uri: 'file:///private/mobile/hit.m4a',
|
||||
name: 'hit.m4a',
|
||||
mimeType: 'application/octet-stream',
|
||||
size: 12,
|
||||
lastModified: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const imported = await send(request('file.importAudio'));
|
||||
|
||||
expect(expectOk(imported).result).toEqual({
|
||||
action: 'selected',
|
||||
fileName: 'hit.m4a',
|
||||
base64Data: MP4_AUDIO_BASE64,
|
||||
mimeType: 'audio/mp4',
|
||||
bytes: 12,
|
||||
});
|
||||
});
|
||||
|
||||
test('file.importAudio 取消选择并拒绝非法 MIME 与超限音频', async () => {
|
||||
vi.mocked(DocumentPicker.getDocumentAsync).mockResolvedValue({
|
||||
canceled: true,
|
||||
|
||||
@@ -45,6 +45,14 @@ const HOST_BRIDGE_AUDIO_MIME_TYPES = new Set<HostBridgeAudioMimeType>([
|
||||
'audio/ogg',
|
||||
'audio/webm',
|
||||
]);
|
||||
export const MOBILE_AUDIO_DOCUMENT_PICKER_TYPES = [
|
||||
'audio/*',
|
||||
'audio/mpeg',
|
||||
'audio/mp4',
|
||||
'audio/wav',
|
||||
'audio/ogg',
|
||||
'audio/webm',
|
||||
];
|
||||
const BASE64_ALPHABET =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
@@ -617,13 +625,7 @@ export async function importAudioFile(): Promise<FileImportAudioResult> {
|
||||
const result = await DocumentPicker.getDocumentAsync({
|
||||
copyToCacheDirectory: true,
|
||||
multiple: false,
|
||||
type: [
|
||||
'audio/mpeg',
|
||||
'audio/mp4',
|
||||
'audio/wav',
|
||||
'audio/ogg',
|
||||
'audio/webm',
|
||||
],
|
||||
type: MOBILE_AUDIO_DOCUMENT_PICKER_TYPES,
|
||||
});
|
||||
if (result.canceled) {
|
||||
throw {
|
||||
|
||||
Reference in New Issue
Block a user