1
This commit is contained in:
@@ -78,6 +78,7 @@ afterEach(() => {
|
||||
__MATCH3D_KEEP_3D_TEST_RENDER__?: boolean;
|
||||
}
|
||||
).__MATCH3D_KEEP_3D_TEST_RENDER__;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
function renderRuntime(
|
||||
@@ -475,6 +476,74 @@ test('运行态会换签并渲染抓大鹅中心容器 UI 图', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('运行态从任意素材读取作品级背景音乐并换签播放', async () => {
|
||||
const run = startLocalMatch3DRun(3);
|
||||
const playSpy = vi
|
||||
.spyOn(HTMLMediaElement.prototype, 'play')
|
||||
.mockResolvedValue(undefined);
|
||||
vi.spyOn(globalThis, 'fetch').mockImplementation((input) => {
|
||||
const url = String(input);
|
||||
const signedUrl = url.includes('legacyPublicPath')
|
||||
? 'https://oss.example.com/match3d-music.mp3'
|
||||
: 'https://oss.example.com/match3d-view.png';
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
read: {
|
||||
signedUrl,
|
||||
expiresAt: new Date(Date.now() + 60_000).toISOString(),
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
const generatedItemAssets: Match3DGeneratedItemAsset[] = [
|
||||
{
|
||||
itemId: 'match3d-item-1',
|
||||
itemName: '草莓',
|
||||
imageSrc: '/match3d/strawberry.png',
|
||||
imageObjectKey: null,
|
||||
imageViews: [],
|
||||
status: 'image_ready',
|
||||
modelSrc: null,
|
||||
modelObjectKey: null,
|
||||
},
|
||||
{
|
||||
itemId: 'match3d-item-2',
|
||||
itemName: '苹果',
|
||||
imageSrc: '/match3d/apple.png',
|
||||
imageObjectKey: null,
|
||||
imageViews: [],
|
||||
status: 'image_ready',
|
||||
modelSrc: null,
|
||||
modelObjectKey: null,
|
||||
backgroundMusic: {
|
||||
taskId: 'music-task-1',
|
||||
provider: 'vector-engine-suno',
|
||||
assetObjectId: 'asset-music-1',
|
||||
assetKind: 'match3d_background_music',
|
||||
audioSrc: '/generated-match3d-assets/audio/music.mp3',
|
||||
prompt: '',
|
||||
title: '果园轻舞',
|
||||
updatedAt: '2026-05-14T00:00:00.000Z',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
renderRuntime(run, generatedItemAssets);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('抓大鹅背景音乐').getAttribute('src')).toBe(
|
||||
'https://oss.example.com/match3d-music.mp3',
|
||||
);
|
||||
});
|
||||
await waitFor(() => expect(playSpy).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
test('本地试玩按难度档位生成类型并兼容历史硬核消除数', () => {
|
||||
const smallRun = startLocalMatch3DRun(12);
|
||||
const hardRun = startLocalMatch3DRun(20);
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from '../../services/assetReadUrlService';
|
||||
import {
|
||||
getMatch3DGeneratedImageViewSources,
|
||||
normalizeMatch3DGeneratedItemAssetsForRuntime,
|
||||
} from '../../services/match3dGeneratedModelCache';
|
||||
import {
|
||||
DEFAULT_RUNTIME_LEVEL_AUDIO_CONFIG,
|
||||
@@ -481,6 +482,10 @@ export function Match3DRuntimeShell({
|
||||
useState('');
|
||||
const musicVolume = authUi?.musicVolume ?? DEFAULT_MATCH3D_MUSIC_VOLUME;
|
||||
const levelAudioConfig = DEFAULT_RUNTIME_LEVEL_AUDIO_CONFIG;
|
||||
const runtimeGeneratedItemAssets = useMemo(
|
||||
() => normalizeMatch3DGeneratedItemAssetsForRuntime(generatedItemAssets),
|
||||
[generatedItemAssets],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeLeftMs(run?.remainingMs ?? 0);
|
||||
@@ -559,7 +564,7 @@ export function Match3DRuntimeShell({
|
||||
|
||||
const backgroundAssetSrc =
|
||||
backgroundImageSrc?.trim() ||
|
||||
generatedItemAssets
|
||||
runtimeGeneratedItemAssets
|
||||
.map(
|
||||
(asset) =>
|
||||
asset.backgroundAsset?.imageSrc?.trim() ||
|
||||
@@ -569,7 +574,7 @@ export function Match3DRuntimeShell({
|
||||
.find(Boolean) ||
|
||||
'';
|
||||
const containerAssetSrc =
|
||||
generatedItemAssets
|
||||
runtimeGeneratedItemAssets
|
||||
.map(
|
||||
(asset) =>
|
||||
asset.backgroundAsset?.containerImageSrc?.trim() ||
|
||||
@@ -578,8 +583,8 @@ export function Match3DRuntimeShell({
|
||||
)
|
||||
.find(Boolean) || '';
|
||||
const imageSourcesByType = useMemo(
|
||||
() => buildMatch3DImageSourcesByType(run, generatedItemAssets),
|
||||
[generatedItemAssets, run],
|
||||
() => buildMatch3DImageSourcesByType(run, runtimeGeneratedItemAssets),
|
||||
[runtimeGeneratedItemAssets, run],
|
||||
);
|
||||
const imageSourceSignature = useMemo(
|
||||
() => buildMatch3DImageSourceSignature(imageSourcesByType),
|
||||
@@ -597,7 +602,7 @@ export function Match3DRuntimeShell({
|
||||
[imageSourcesByType, resolvedImageSources],
|
||||
);
|
||||
const backgroundMusicSrc =
|
||||
generatedItemAssets.find((asset) => asset.backgroundMusic?.audioSrc)
|
||||
runtimeGeneratedItemAssets.find((asset) => asset.backgroundMusic?.audioSrc)
|
||||
?.backgroundMusic?.audioSrc ?? null;
|
||||
const [resolvedBackgroundMusicSrc, setResolvedBackgroundMusicSrc] = useState('');
|
||||
const [resolvedContainerImageSrc, setResolvedContainerImageSrc] = useState('');
|
||||
@@ -605,7 +610,7 @@ export function Match3DRuntimeShell({
|
||||
if (!run) {
|
||||
return new Map<string, string>();
|
||||
}
|
||||
const readyAssets = generatedItemAssets.filter(
|
||||
const readyAssets = runtimeGeneratedItemAssets.filter(
|
||||
(asset) => asset.clickSound?.audioSrc,
|
||||
);
|
||||
const sortedTypes = [
|
||||
@@ -617,7 +622,7 @@ export function Match3DRuntimeShell({
|
||||
return src ? [[typeId, src] as const] : [];
|
||||
}),
|
||||
);
|
||||
}, [generatedItemAssets, run]);
|
||||
}, [runtimeGeneratedItemAssets, run]);
|
||||
|
||||
const tryPlayBackgroundMusic = useCallback(() => {
|
||||
const audio = backgroundAudioRef.current;
|
||||
@@ -879,6 +884,7 @@ export function Match3DRuntimeShell({
|
||||
src={resolvedBackgroundMusicSrc}
|
||||
loop
|
||||
preload="auto"
|
||||
aria-label="抓大鹅背景音乐"
|
||||
/>
|
||||
) : null}
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user