#include "stdafx.h" /** * @file TetrisMedia.cpp * @brief 实现背景音乐开关和复活视频播放逻辑。 */ #include "Tetris.h" #include "TetrisAppInternal.h" #include "TetrisAssets.h" #include #include static bool bgmPlaying = false; static bool bgmUsingMci = false; static constexpr const wchar_t* kBgmAlias = L"TereisBgm"; static constexpr const wchar_t* kReviveVideoAlias = L"TereisReviveVideo"; /** * @brief 尝试通过 MCI 循环播放指定音乐文件。 * @param path 音频文件路径。 * @param forceMpegVideo 是否强制按 mpegvideo 类型打开。 * @return 播放成功返回 true,否则返回 false。 */ static bool TryPlayMciLoop(const std::wstring& path, bool forceMpegVideo) { if (!FileExists(path)) { return false; } mciSendStringW((std::wstring(L"close ") + kBgmAlias).c_str(), nullptr, 0, nullptr); // MCI 对部分 OGG/视频容器识别不稳定,调用方会按不同类型尝试。 std::wstring openCommand = L"open \"" + path + L"\" "; if (forceMpegVideo) { openCommand += L"type mpegvideo "; } openCommand += L"alias "; openCommand += kBgmAlias; if (mciSendStringW(openCommand.c_str(), nullptr, 0, nullptr) != 0) { return false; } std::wstring playCommand = std::wstring(L"play ") + kBgmAlias + L" repeat"; if (mciSendStringW(playCommand.c_str(), nullptr, 0, nullptr) != 0) { mciSendStringW((std::wstring(L"close ") + kBgmAlias).c_str(), nullptr, 0, nullptr); return false; } bgmPlaying = true; bgmUsingMci = true; return true; } /** * @brief 停止背景音乐并释放当前使用的播放设备。 */ void StopBackgroundMusic() { if (bgmUsingMci) { mciSendStringW((std::wstring(L"stop ") + kBgmAlias).c_str(), nullptr, 0, nullptr); mciSendStringW((std::wstring(L"close ") + kBgmAlias).c_str(), nullptr, 0, nullptr); } else { PlaySoundW(nullptr, nullptr, 0); } bgmPlaying = false; bgmUsingMci = false; } /** * @brief 按资源优先级查找并启动背景音乐。 */ void StartBackgroundMusic() { if (!bgmEnabled || bgmPlaying) { return; } const wchar_t* bgmWavRelativePath = L"assets\\audio\\bgm.wav"; const std::wstring bgmWavCandidates[] = { BuildAssetPath(bgmWavRelativePath), BuildWorkingDirAssetPath(bgmWavRelativePath) }; for (const std::wstring& candidate : bgmWavCandidates) { if (FileExists(candidate) && PlaySoundW(candidate.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_LOOP)) { bgmPlaying = true; bgmUsingMci = false; return; } } const wchar_t* oggRelativePath = L"assets\\audio\\bgm.ogg"; const std::wstring oggCandidates[] = { BuildAssetPath(oggRelativePath), BuildWorkingDirAssetPath(oggRelativePath) }; for (const std::wstring& candidate : oggCandidates) { if (TryPlayMciLoop(candidate, false) || TryPlayMciLoop(candidate, true)) { return; } } const wchar_t* fallbackWavRelativePath = L"assets\\audio\\background.wav"; const std::wstring fallbackWavCandidates[] = { BuildAssetPath(fallbackWavRelativePath), BuildWorkingDirAssetPath(fallbackWavRelativePath) }; for (const std::wstring& candidate : fallbackWavCandidates) { if (FileExists(candidate) && PlaySoundW(candidate.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_LOOP)) { bgmPlaying = true; bgmUsingMci = false; return; } } bgmEnabled = false; } /** * @brief 切换背景音乐开关并刷新窗口。 * @param hWnd 当前窗口句柄。 */ void ToggleBackgroundMusic(HWND hWnd) { bgmEnabled = !bgmEnabled; if (bgmEnabled) { StartBackgroundMusic(); } else { StopBackgroundMusic(); } InvalidateRect(hWnd, nullptr, FALSE); } /** * @brief 播放复活视频,先尝试 MCI,全屏播放失败时退回系统默认播放器。 * @param hWnd 当前窗口句柄,用作 MCI 父窗口和 ShellExecute 父窗口。 * @return 播放成功返回 true,否则返回 false。 */ bool PlayReviveVideo(HWND hWnd) { std::wstring videoPath = BuildAssetPath(L"assets\\video\\video.avi"); if (!FileExists(videoPath)) { videoPath = BuildWorkingDirAssetPath(L"assets\\video\\video.avi"); } if (!FileExists(videoPath)) { videoPath = BuildAssetPath(L"assets\\video\\video.mp4"); } if (!FileExists(videoPath)) { videoPath = BuildWorkingDirAssetPath(L"assets\\video\\video.mp4"); } if (!FileExists(videoPath)) { return false; } bool shouldResumeBgm = bgmEnabled; if (bgmPlaying) { StopBackgroundMusic(); } // 先用 MCI 全屏同步播放;失败时再交给系统默认播放器。 bool played = false; for (int attempt = 0; attempt < 2 && !played; attempt++) { bool forceMpegVideo = attempt == 0; mciSendStringW((std::wstring(L"close ") + kReviveVideoAlias).c_str(), nullptr, 0, nullptr); std::wstring openCommand = L"open \"" + videoPath + L"\" "; if (forceMpegVideo) { openCommand += L"type mpegvideo "; } openCommand += L"alias "; openCommand += kReviveVideoAlias; if (mciSendStringW(openCommand.c_str(), nullptr, 0, hWnd) == 0) { std::wstring playCommand = std::wstring(L"play ") + kReviveVideoAlias + L" fullscreen wait"; MCIERROR playResult = mciSendStringW(playCommand.c_str(), nullptr, 0, hWnd); mciSendStringW((std::wstring(L"close ") + kReviveVideoAlias).c_str(), nullptr, 0, nullptr); played = playResult == 0; } } if (!played) { SHELLEXECUTEINFOW shellInfo = {}; shellInfo.cbSize = sizeof(shellInfo); shellInfo.fMask = SEE_MASK_NOCLOSEPROCESS; shellInfo.hwnd = hWnd; shellInfo.lpVerb = L"open"; shellInfo.lpFile = videoPath.c_str(); shellInfo.nShow = SW_SHOWNORMAL; if (ShellExecuteExW(&shellInfo)) { if (shellInfo.hProcess != nullptr) { WaitForSingleObject(shellInfo.hProcess, INFINITE); CloseHandle(shellInfo.hProcess); } played = true; } } if (shouldResumeBgm) { StartBackgroundMusic(); } return played; }