Skip to content

Commit 9409f2a

Browse files
author
MEBTTE
authored
Merge pull request #126 from mebtte/beta
check usability of music file
2 parents ddce75e + c7536ba commit 9409f2a

8 files changed

Lines changed: 99 additions & 50 deletions

File tree

apps/cli/src/commands/start_server/form_app/controllers/upload_asset.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const ASSET_TYPE_MAP_OPTION: Record<
6666

6767
export default async (ctx: Context) => {
6868
const { field, file } = await parseFormdata<'assetType', 'asset'>(ctx.req);
69-
// @ts-expect-error
69+
// @ts-expect-error: known type
7070
const assetType: AssetType | undefined = field.assetType
7171
? field.assetType[0]
7272
: undefined;
@@ -75,12 +75,12 @@ export default async (ctx: Context) => {
7575
return ctx.except(ExceptionCode.WRONG_PARAMETER);
7676
}
7777

78-
const { maxSize, acceptTypes } = ASSET_TYPE_MAP[assetType];
78+
const { maxSize, acceptType } = ASSET_TYPE_MAP[assetType];
7979
if (asset.size > maxSize) {
8080
return ctx.except(ExceptionCode.ASSET_OVERSIZE);
8181
}
8282
const ft = await fileType.fromFile(asset.path);
83-
if (!ft || !acceptTypes.includes(ft.mime)) {
83+
if (!ft || !new Set(Object.values(acceptType).flat()).has(ft.mime)) {
8484
return ctx.except(ExceptionCode.WRONG_ASSET_TYPE);
8585
}
8686

apps/pwa/src/components/file_select.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import styled, { css } from 'styled-components';
2-
import upperCaseFirstLetter from '@/style/upper_case_first_letter';
32
import { CSSVariable } from '../global_style';
43
import useEvent from '../utils/use_event';
54
import selectFile from '../utils/select_file';
@@ -16,6 +15,10 @@ const Style = styled.div<{ disabled: boolean }>`
1615
user-select: none;
1716
word-break: break-all;
1817
18+
> .placeholder {
19+
color: ${CSSVariable.TEXT_COLOR_SECONDARY};
20+
}
21+
1922
&:active {
2023
border-color: ${CSSVariable.COLOR_PRIMARY};
2124
border-style: solid;
@@ -60,7 +63,7 @@ function FileSelect({
6063

6164
return (
6265
<Style onClick={onSelectFile} disabled={disabled}>
63-
{value ? value.name : placeholder}
66+
{value ? value.name : <span className="placeholder">{placeholder}</span>}
6467
</Style>
6568
);
6669
}

apps/pwa/src/i18n/en_us.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,4 +223,5 @@ export default {
223223
feedback: 'feedback',
224224
fork_from_these_musics: 'fork from these musics',
225225
forked_by_these_musics: 'forked by these musics',
226+
can_not_play_audio_file: 'this file can not be played',
226227
};

apps/pwa/src/i18n/zh_hans.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ const zhCN: {
211211
feedback: '反馈',
212212
fork_from_these_musics: '二次创作自以下音乐',
213213
forked_by_these_musics: '被以下音乐二次创作',
214+
can_not_play_audio_file: '音乐文件无法被播放',
214215
};
215216

216217
export default zhCN;

apps/pwa/src/pages/player/music_drawer/edit_menu.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import playerEventemitter, {
5757
} from '../eventemitter';
5858
import MusicInfo from '../components/music_info';
5959
import MissingSinger from '../components/missing_singer';
60+
import upperCaseFirstLetter from '#/utils/upper_case_first_letter';
6061

6162
interface Singer {
6263
id: string;
@@ -378,10 +379,16 @@ function EditMenu({ music }: { music: MusicDetail }) {
378379
dialog.fileSelect({
379380
title: t('modify_file_of_music'),
380381
label: t('file_of_music'),
381-
acceptTypes: ASSET_TYPE_MAP[AssetType.MUSIC].acceptTypes,
382-
placeholder: t(
383-
'one_of_formats',
384-
ASSET_TYPE_MAP[AssetType.MUSIC].acceptTypes.join(','),
382+
acceptTypes: Object.values(
383+
ASSET_TYPE_MAP[AssetType.MUSIC].acceptType,
384+
).flat(),
385+
placeholder: upperCaseFirstLetter(
386+
t(
387+
'one_of_formats',
388+
Object.keys(ASSET_TYPE_MAP[AssetType.MUSIC].acceptType).join(
389+
'/',
390+
),
391+
),
385392
),
386393
onConfirm: async (file) => {
387394
if (!file) {

apps/pwa/src/pages/player/pages/my_music/create_music_dialog/index.tsx

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import playerEventemitter, {
4040
} from '../../../eventemitter';
4141
import { Singer } from './constants';
4242
import upperCaseFirstLetter from '#/utils/upper_case_first_letter';
43-
import { base64ToCover } from './utils';
43+
import { base64ToCover, canAudioPlay } from './utils';
4444

4545
const maskProps: { style: CSSProperties } = {
4646
style: { zIndex: ZIndex.DIALOG },
@@ -90,33 +90,43 @@ function CreateMusicDialog() {
9090
setMusicType(option.actualValue);
9191

9292
const [asset, setAsset] = useState<File | null>(null);
93-
const onAssetChange = (a) => {
93+
const onAssetChange = (a: File | null) => {
9494
setAsset(a);
9595

96-
getMusicFileMetadata(a)
97-
.then((metadata) => {
98-
const { title, artist } = metadata;
99-
if (!name && title) {
100-
setName(title);
96+
if (a) {
97+
canAudioPlay(a).then((canPlay) => {
98+
if (!canPlay) {
99+
setAsset(null);
100+
return notice.error(t('can_not_play_audio_file'));
101101
}
102-
if (!singerList.length && artist) {
103-
searchSingerRequest({
104-
keyword: artist,
105-
page: 1,
106-
pageSize: 10,
107-
requestMinimalDuration: 0,
108-
})
109-
.then((data) => {
110-
if (!singerList.length) {
111-
setSingerList(data.singerList);
112-
}
102+
});
103+
getMusicFileMetadata(a)
104+
.then((metadata) => {
105+
const { title, artist } = metadata;
106+
if (!name && title) {
107+
setName(title);
108+
}
109+
if (!singerList.length && artist) {
110+
searchSingerRequest({
111+
keyword: artist,
112+
page: 1,
113+
pageSize: 10,
114+
requestMinimalDuration: 0,
113115
})
114-
.catch((error) => logger.error(error, 'Failed to search singers'));
115-
}
116-
})
117-
.catch((error) =>
118-
logger.error(error, "Failed to parse music's metadata"),
119-
);
116+
.then((data) => {
117+
if (!singerList.length) {
118+
setSingerList(data.singerList);
119+
}
120+
})
121+
.catch((error) =>
122+
logger.error(error, 'Failed to search singers'),
123+
);
124+
}
125+
})
126+
.catch((error) =>
127+
logger.error(error, "Failed to parse music's metadata"),
128+
);
129+
}
120130
};
121131

122132
const [loading, setLoading] = useState(false);
@@ -229,11 +239,13 @@ function CreateMusicDialog() {
229239
value={asset}
230240
onChange={onAssetChange}
231241
disabled={loading}
232-
acceptTypes={ASSET_TYPE_MAP[AssetType.MUSIC].acceptTypes}
242+
acceptTypes={Object.values(
243+
ASSET_TYPE_MAP[AssetType.MUSIC].acceptType,
244+
).flat()}
233245
placeholder={upperCaseFirstLetter(
234-
`${t('empty_file_warning')}, ${t(
235-
'supported_formats',
236-
)} ${ASSET_TYPE_MAP[AssetType.MUSIC].acceptTypes.join(', ')}`,
246+
`${t('supported_formats')}: ${Object.keys(
247+
ASSET_TYPE_MAP[AssetType.MUSIC].acceptType,
248+
).join('/')}`,
237249
)}
238250
/>
239251
</Label>

apps/pwa/src/pages/player/pages/my_music/create_music_dialog/utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,18 @@ export async function base64ToCover(base64: string) {
2525
canvas.toBlob((blob) => resolve(blob!), 'image/jpeg', 0.8),
2626
);
2727
}
28+
29+
export function canAudioPlay(file: File) {
30+
const url = URL.createObjectURL(file);
31+
const audio = new Audio();
32+
return new Promise<boolean>((resolve) => {
33+
audio.muted = true;
34+
audio.autoplay = true;
35+
audio.onplay = () => resolve(true);
36+
audio.onerror = () => resolve(false);
37+
audio.src = url;
38+
}).finally(() => {
39+
audio.pause();
40+
URL.revokeObjectURL(url);
41+
});
42+
}

shared/constants/index.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,45 @@ export const ASSET_TYPES = Object.values(AssetType);
2525
export const ASSET_TYPE_MAP: Record<
2626
AssetType,
2727
{
28-
acceptTypes: string[];
28+
acceptType: Record<string, string[]>;
2929
maxSize: number;
3030
}
3131
> = {
3232
[AssetType.SINGER_AVATAR]: {
33-
acceptTypes: ['image/jpeg'],
33+
acceptType: {
34+
jpg: ['image/jpeg'],
35+
jpeg: ['image/jpeg'],
36+
},
3437
maxSize: 1024 * 1024 * 2,
3538
},
3639
[AssetType.MUSICBILL_COVER]: {
37-
acceptTypes: ['image/jpeg'],
40+
acceptType: {
41+
jpg: ['image/jpeg'],
42+
jpeg: ['image/jpeg'],
43+
},
3844
maxSize: 1024 * 1024 * 2,
3945
},
4046
[AssetType.MUSIC_COVER]: {
41-
acceptTypes: ['image/jpeg'],
47+
acceptType: {
48+
jpg: ['image/jpeg'],
49+
jpeg: ['image/jpeg'],
50+
},
4251
maxSize: 1024 * 1024 * 2,
4352
},
4453
[AssetType.USER_AVATAR]: {
45-
acceptTypes: ['image/jpeg'],
54+
acceptType: {
55+
jpg: ['image/jpeg'],
56+
jpeg: ['image/jpeg'],
57+
},
4658
maxSize: 1024 * 1024 * 2,
4759
},
4860
[AssetType.MUSIC]: {
49-
acceptTypes: [
50-
'audio/mpeg', // .mp3
51-
'audio/mp4', // .mp4
52-
'audio/flac', // .flac
53-
'audio/x-flac', // .flac
54-
'audio/m4a', // .m4a
55-
'audio/x-m4a', // .m4a
56-
],
61+
acceptType: {
62+
mp3: ['audio/mpeg'],
63+
flac: ['audio/flac', 'audio/x-flac'],
64+
m4a: ['audio/m4a', 'audio/x-m4a'],
65+
mp4: ['audio/mp4', 'video/mp4'],
66+
},
5767
maxSize: 1024 * 1024 * 200,
5868
},
5969
};

0 commit comments

Comments
 (0)