• 游客 你好!
    ✿ MoeBBS 全部交流群公示 ✿
    诋毁本身就是一种仰望👑
  • 论坛资源声明
    1.本论坛所有资源均由用户自行发布,版权归原作者所有,未经允许,禁止非法转载、复制或用于商业用途。
    2.若您认为论坛中的任何资源侵犯了您的合法权益(如版权、肖像权等),请提供相关证明材料通过站内信,邮箱或工单与我们联系,我们将在核实后尽快处理或移除相关内容。
    3.本论坛无法100%保证用户发布内容的准确性、完整性或合法性,使用相关资源前请您自行甄别其风险与适用性,后果由使用者自行承担。
[C&C++] 音乐播放器C++源码和程序打包蓝奏云了, 想魔改的大佬来玩

源码 [C&C++] 音乐播放器C++源码和程序打包蓝奏云了, 想魔改的大佬来玩

  • 主题发起人 主题发起人 God
  • 开始时间 开始时间
这是隐藏的内容

God

有你才完美~
SVIP
User
声誉:23%
注册
2025/02/13
消息
74
柚币
2,664.6Y
米币
0.0M
1760776031104.webp

C++:
// 音乐播放器.cpp - 完整优化版(CSV快速存储歌曲信息)
#include "framework.h"
#include "音乐播放器.h"
#include <commdlg.h>
#include <mmsystem.h>
#include <shlwapi.h>
#include <shlobj.h>
#include <commctrl.h>
#include <tchar.h>
#include <vector>
#include <string>
#include <algorithm>
#include <stdint.h>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <atomic>
#include <random>
#include <sstream>
#include "sqlite3.h"
 
// 禁用C++17 deprecated警告
#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
 
// FFmpeg 核心头文件
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
}
 
// 库依赖声明
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "avutil.lib")
 
// 公共控件初始化
#pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
 
// 控件ID与自定义消息定义
#define MAX_LOADSTRING 100
#define IDC_PLAY_BUTTON    1001
#define IDC_PREV_BUTTON    1002
#define IDC_NEXT_BUTTON    1003
#define IDC_ADD_FILE_BUTTON 1004
#define IDC_ADD_FOLDER_BUTTON 1005
#define IDC_CLEAR_LIST_BUTTON 1010
#define IDC_SONG_LIST      1006  // ListView控件
#define IDC_TITLE_LABEL    1007
#define IDC_VOLUME_SLIDER  1008
#define IDC_MODE_BUTTON    1009
#define IDC_SEARCH_EDIT    1011
#define IDC_SEARCH_BUTTON  1012
#define IDC_PROGRESS_BAR   1013  // 进度条控件ID
#define IDC_PROGRESS_TEXT  1014  // 进度文本显示控件
#define WM_USER_PLAY_COMPLETE (WM_USER + 1)
#define WM_USER_UPDATE_PROGRESS (WM_USER + 2)  // 更新进度条的自定义消息
#define PROGRESS_UPDATE_INTERVAL 100  // 进度条更新间隔(毫秒)
#define PROGRESS_TIMER_ID 2          // 进度更新定时器ID
 
// 缓冲区配置
const int N_BUFFERS = 100;
const float MIN_BUFFER_SIZE_MB = 300.0f;
const float MAX_BUFFER_SIZE_MB = 300.0f;
const int MIN_BUFFER_SIZE = static_cast<int>(MIN_BUFFER_SIZE_MB * 1024 * 1024);
const int MAX_BUFFER_SIZE = static_cast<int>(MAX_BUFFER_SIZE_MB * 1024 * 1024);
 
// 字符串缓冲区大小定义
#define LARGE_BUFFER_SIZE 9999
#define MEDIUM_BUFFER_SIZE 9999
#define SMALL_BUFFER_SIZE 9999
 
// 播放模式枚举
enum class PlayMode {
    AllLoop,
    SingleLoop,
    Random
};
 
// 歌曲信息结构体
struct SongInfo {
    std::wstring filePath;
    std::wstring fileName;
    std::wstring fileSize;  // 格式化后的文件大小
    std::wstring bitrate;   // 格式化后的比特率
    int64_t sizeBytes;      // 原始字节数
    int bitrateKbps;        // 原始比特率
    double duration;        // 歌曲时长(秒)
};
 
// 全局变量
HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];
HFONT hFont = NULL;
HFONT hLargeFont = NULL;
HWND g_hwndMain = NULL;
HWND g_hwndSongList = NULL;
HWND g_hwndTitleLabel = NULL;
HWND g_hwndModeButton = NULL;
HWND g_hwndProgressBar = NULL;  // 进度条句柄
HWND g_hwndProgressText = NULL; // 进度文本句柄
static sqlite3* g_db = nullptr;   // 全局数据库句柄
std::vector<SongInfo> g_songList;
int g_lastUpdatedSongCount = 1;
int g_currentSongIndex = 1;
BOOL g_isPlaying = FALSE;
PlayMode g_currentMode = PlayMode::AllLoop;
std::mt19937 g_randEngine(std::random_device{}());
 
// FFmpeg与WaveOut上下文
AVFormatContext* fmt_ctx = NULL;
AVCodecContext* dec_ctx = NULL;
SwrContext* swr_ctx = NULL;
AVPacket* pkt = NULL;
AVFrame* frame = NULL;
int audio_stream_idx = -1;
HWAVEOUT hWaveOut = NULL;
std::atomic<bool> is_playing_ffmpeg(false);
double g_totalDuration = 0.0;          // 总时长(秒)
std::atomic<double> g_playedDuration = 0.0;  // 已播放时长(秒)
const double DURATION_TOLERANCE = 0;
std::atomic<double> g_lastUpdateTime = 0.0;  // 上次更新时间(用于实时计算)
std::atomic<double> g_playbackSpeed = 1.0;   // 播放速度(用于准确计算)
 
// 多缓冲结构体
struct PCMBuffer {
    WAVEHDR hdr = { 0 };
    std::vector<uint8_t> data;
    bool prepared = false;
    double duration;  // 该缓冲区的音频时长(秒)
};
std::vector<PCMBuffer> g_buffers;
std::queue<int> g_freeBuffers;
std::mutex g_bufMutex;
std::condition_variable g_bufCv;
std::thread g_decodeThread;
std::atomic<bool> g_stopDecode(false);
 
// 函数声明
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
void InitPlayerUI(HWND hWnd);
void PlaySong(const SongInfo& song);
void StopPlayback();
void PausePlayback();
void ResumePlayback();
void AddFiles(HWND hWnd);
void AddFolder(HWND hWnd);
void ClearSongList();
void UpdateSongListUI();
void SavePlaylist();
void LoadPlaylist();
void SetVolume(int volume);
void EnumMusicFiles(const std::wstring& folder);
void InitFFmpeg();
bool OpenAudioFile(const std::wstring& filePath, double& duration);
void CloseAudioFile();
bool DecodeAudioFrame(std::vector<uint8_t>& pcm, double& frameDuration);
void PlayPCMData(const std::vector<uint8_t>& pcm, double duration, int bufIndex);
std::string WstringToUtf8(const std::wstring& wstr);
void UpdateModeButtonText();
int GetNextIndex();
int GetPrevIndex();
void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
void DecodeThreadFunc();
std::wstring GetFileSizeString(const std::wstring& filePath, int64_t& sizeBytes);
std::wstring GetBitrateString(const std::wstring& filePath, int& bitrateKbps);
std::wstring FormatDuration(double seconds);
void UpdateProgressDisplay();
double GetCurrentTimeSeconds();
 
// CSV处理辅助函数
std::string WstringToUtf8Escaped(const std::wstring& wstr);
std::wstring Utf8ToWstring(const std::string& utf8);
std::vector<std::string> SplitCsvLine(const std::string& line);
 
int WINAPI wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
 
    INITCOMMONCONTROLSEX icex;
    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC = ICC_BAR_CLASSES | ICC_LISTVIEW_CLASSES;
    InitCommonControlsEx(&icex);
 
    wcscpy_s(szTitle, L"音乐播放器");
    wcscpy_s(szWindowClass, L"MusicPlayerClass");
    MyRegisterClass(hInstance);
 
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }
 
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY));
    InitFFmpeg();
    LoadPlaylist();
    UpdateModeButtonText();
 
    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (hAccelTable && !TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else if (!hAccelTable)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
 
    SavePlaylist();
    StopPlayback();
 
    if (hFont) DeleteObject(hFont);
    if (hLargeFont) DeleteObject(hLargeFont);
 
    return (int)msg.wParam;
}
 
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;
 
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MY);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
 
    return RegisterClassExW(&wcex);
}
 
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance;
 
    HWND hWnd = CreateWindowW(szWindowClass, szTitle,
        WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX,
        CW_USEDEFAULT, 100, 1000, 650, nullptr, NULL, hInstance, nullptr);
 
    if (!hWnd)
    {
        return FALSE;
    }
 
    g_hwndMain = hWnd;
    InitPlayerUI(hWnd);
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
 
    return TRUE;
}
 
void InitPlayerUI(HWND hWnd)
{
    hFont = CreateFontW(20, 0, 0, 0, FW_NORMAL,
        FALSE, FALSE, FALSE, DEFAULT_CHARSET,
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"微软雅黑");
 
    hLargeFont = CreateFontW(20, 0, 0, 0, FW_BOLD,
        FALSE, FALSE, FALSE, DEFAULT_CHARSET,
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"微软雅黑");
 
    // 播放控制按钮
    HWND hPrevBtn = CreateWindowW(L"BUTTON", L"&#9664;",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
        20, 20, 50, 50, hWnd, (HMENU)IDC_PREV_BUTTON, hInst, NULL);
 
    HWND hPlayBtn = CreateWindowW(L"BUTTON", L"&#9654;",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
        80, 20, 50, 50, hWnd, (HMENU)IDC_PLAY_BUTTON, hInst, NULL);
 
    HWND hNextBtn = CreateWindowW(L"BUTTON", L"&#9654;&#9654;",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
        140, 20, 50, 50, hWnd, (HMENU)IDC_NEXT_BUTTON, hInst, NULL);
 
    // 模式切换按钮
    g_hwndModeButton = CreateWindowW(L"BUTTON", L"全部循环",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
        200, 20, 120, 50, hWnd, (HMENU)IDC_MODE_BUTTON, hInst, NULL);
 
    // 文件操作按钮
    HWND hAddFileBtn = CreateWindowW(L"BUTTON", L"添加文件",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
        330, 20, 120, 50, hWnd, (HMENU)IDC_ADD_FILE_BUTTON, hInst, NULL);
 
    HWND hAddFolderBtn = CreateWindowW(L"BUTTON", L"添加文件夹",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
        460, 20, 120, 50, hWnd, (HMENU)IDC_ADD_FOLDER_BUTTON, hInst, NULL);
 
    HWND hClearListBtn = CreateWindowW(L"BUTTON", L"清空列表",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
        600, 20, 120, 50, hWnd, (HMENU)IDC_CLEAR_LIST_BUTTON, hInst, NULL);
 
    // 音量控制
    CreateWindowW(L"STATIC", L"音量:",
        WS_CHILD | WS_VISIBLE | SS_CENTER,
        740, 35, 50, 20, hWnd, NULL, hInst, NULL);
 
    HWND hVolumeSlider = CreateWindowW(TRACKBAR_CLASS, L"",
        WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_NOTICKS | TBS_FIXEDLENGTH,
        790, 30, 60, 30, hWnd, (HMENU)IDC_VOLUME_SLIDER, hInst, NULL);
    SendMessage(hVolumeSlider, TBM_SETRANGE, TRUE, MAKELPARAM(0, 100));
    SendMessage(hVolumeSlider, TBM_SETPOS, TRUE, 70);
    SetVolume(70);
 
    // 标题(显示歌曲名+文件夹路径)
    g_hwndTitleLabel = CreateWindowW(L"STATIC", L"等待播放",
        WS_CHILD | WS_VISIBLE | SS_CENTER | WS_BORDER,
        20, 80, 900, 40, hWnd, (HMENU)IDC_TITLE_LABEL, hInst, NULL);
 
    // 进度条(可拖动)
    g_hwndProgressBar = CreateWindowW(TRACKBAR_CLASS, L"",
        WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_NOTICKS | TBS_FIXEDLENGTH,
        20, 125, 800, 30, hWnd, (HMENU)IDC_PROGRESS_BAR, hInst, NULL);
    SendMessage(g_hwndProgressBar, TBM_SETRANGE, TRUE, MAKELPARAM(0, 1000)); // 0~1000 精度
    SendMessage(g_hwndProgressBar, TBM_SETPOS, TRUE, 0);
 
    // 进度文本显示(显示当前时间/总时间)
    g_hwndProgressText = CreateWindowW(L"STATIC", L"00:00 / 00:00",
        WS_CHILD | WS_VISIBLE | SS_RIGHT,
        830, 135, 90, 20, hWnd, (HMENU)IDC_PROGRESS_TEXT, hInst, NULL);
 
    // 搜索控件
    CreateWindowW(L"STATIC", L"搜索:",
        WS_CHILD | WS_VISIBLE | SS_LEFT,
        20, 165, 50, 25, hWnd, NULL, hInst, NULL);
 
    HWND hSearchEdit = CreateWindowW(L"EDIT", L"",
        WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
        80, 165, 650, 25, hWnd, (HMENU)IDC_SEARCH_EDIT, hInst, NULL);
 
    HWND hSearchBtn = CreateWindowW(L"BUTTON", L"定位",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
        740, 165, 80, 25, hWnd, (HMENU)IDC_SEARCH_BUTTON, hInst, NULL);
 
    // 歌曲列表(ListView控件)
    g_hwndSongList = CreateWindowW(WC_LISTVIEW, L"",
        WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | LVS_REPORT | LVS_SHOWSELALWAYS | WS_BORDER,
        20, 195, 900, 400, hWnd, (HMENU)IDC_SONG_LIST, hInst, NULL);
 
    // 设置ListView列
    LVCOLUMN lvc = { 0 };
    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
 
    // 歌曲名列
    lvc.fmt = LVCFMT_LEFT;
    lvc.cx = 450;
    lvc.pszText = const_cast<LPWSTR>(L"歌曲名");
    ListView_InsertColumn(g_hwndSongList, 0, &lvc);
 
    // 文件大小列
    lvc.fmt = LVCFMT_RIGHT;
    lvc.cx = 120;
    lvc.pszText = const_cast<LPWSTR>(L"文件大小");
    ListView_InsertColumn(g_hwndSongList, 1, &lvc);
 
    // 比特率列
    lvc.fmt = LVCFMT_RIGHT;
    lvc.cx = 120;
    lvc.pszText = const_cast<LPWSTR>(L"比特率");
    ListView_InsertColumn(g_hwndSongList, 2, &lvc);
 
    // 时长列
    lvc.fmt = LVCFMT_RIGHT;
    lvc.cx = 110;
    lvc.pszText = const_cast<LPWSTR>(L"时长");
    ListView_InsertColumn(g_hwndSongList, 3, &lvc);
 
    // 设置字体
    SendMessage(hPrevBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
    SendMessage(hPlayBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
    SendMessage(hNextBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
    SendMessage(g_hwndModeButton, WM_SETFONT, (WPARAM)hFont, TRUE);
    SendMessage(hAddFileBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
    SendMessage(hAddFolderBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
    SendMessage(hClearListBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
    SendMessage(g_hwndTitleLabel, WM_SETFONT, (WPARAM)hLargeFont, TRUE);
    SendMessage(g_hwndProgressText, WM_SETFONT, (WPARAM)hFont, TRUE);
    SendMessage(hSearchEdit, WM_SETFONT, (WPARAM)hFont, TRUE);
    SendMessage(hSearchBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
    SendMessage(g_hwndSongList, WM_SETFONT, (WPARAM)hFont, TRUE);
}
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            SavePlaylist();  // 确保退出时保存
            DestroyWindow(hWnd);
            break;
        case IDC_MODE_BUTTON:
        {
            g_currentMode = (g_currentMode == PlayMode::AllLoop) ? PlayMode::SingleLoop :
                (g_currentMode == PlayMode::SingleLoop) ? PlayMode::Random : PlayMode::AllLoop;
            UpdateModeButtonText();
 
            const WCHAR* modeText = L"";
            switch (g_currentMode)
            {
            case PlayMode::AllLoop:
                modeText = L"全部循环";
                break;
            case PlayMode::SingleLoop:
                modeText = L"单曲循环";
                break;
            case PlayMode::Random:
                modeText = L"随机播放";
                break;
            }
 
            WCHAR tip[SMALL_BUFFER_SIZE];
            swprintf_s(tip, SMALL_BUFFER_SIZE, L"当前模式:%s", modeText);
            SetWindowText(g_hwndTitleLabel, tip);
 
            SetTimer(hWnd, 1, 1000, [](HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
                if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size()) {
                    const SongInfo& song = g_songList[g_currentSongIndex];
                    WCHAR fileName[MAX_PATH] = { 0 };
                    wcscpy_s(fileName, PathFindFileNameW(song.filePath.c_str()));
                    WCHAR folderPath[MAX_PATH] = { 0 };
                    wcscpy_s(folderPath, song.filePath.c_str());
                    PathRemoveFileSpecW(folderPath);
 
                    WCHAR fullTitle[LARGE_BUFFER_SIZE] = { 0 };
                    swprintf_s(fullTitle, L"%s (%s)", fileName, folderPath);
                    SetWindowText(g_hwndTitleLabel, fullTitle);
                }
                KillTimer(hwnd, idEvent);
                });
            break;
        }
        case IDC_CLEAR_LIST_BUTTON:
            if (g_songList.empty()) {
                SetWindowText(g_hwndTitleLabel, L"播放列表已为空");
                break;
            }
            StopPlayback();
            ClearSongList();
            g_currentSongIndex = -1;
            UpdateSongListUI();
            SavePlaylist();
            SetWindowText(g_hwndTitleLabel, L"播放列表已清空");
            break;
        case IDC_PLAY_BUTTON:
            if (g_songList.empty()) break;
 
            if (g_isPlaying) {
                PausePlayback();
                SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"&#9654;");
            }
            else {
                if (g_currentSongIndex == -1) g_currentSongIndex = 0;
                if (g_currentSongIndex < (int)g_songList.size()) {
                    ResumePlayback();
                    SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
                }
            }
            g_isPlaying = !g_isPlaying;
            break;
        case IDC_PREV_BUTTON:
            if (g_songList.empty()) break;
 
            StopPlayback();
            g_currentSongIndex = GetPrevIndex();
            PlaySong(g_songList[g_currentSongIndex]);
            g_isPlaying = TRUE;
            SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
            UpdateSongListUI();
            break;
        case IDC_NEXT_BUTTON:
            if (g_songList.empty()) break;
 
            StopPlayback();
            g_currentSongIndex = GetNextIndex();
            PlaySong(g_songList[g_currentSongIndex]);
            g_isPlaying = TRUE;
            SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
            UpdateSongListUI();
            break;
        case IDC_ADD_FILE_BUTTON:
            AddFiles(hWnd);
            break;
        case IDC_ADD_FOLDER_BUTTON:
            AddFolder(hWnd);
            break;
        case IDC_SEARCH_BUTTON:
        {
            if (g_songList.empty()) break;
 
            WCHAR searchText[MEDIUM_BUFFER_SIZE] = { 0 };
            GetDlgItemTextW(hWnd, IDC_SEARCH_EDIT, searchText, MEDIUM_BUFFER_SIZE);
 
            if (wcslen(searchText) == 0) {
                MessageBoxW(hWnd, L"请输入搜索内容", L"提示", MB_OK | MB_ICONINFORMATION);
                break;
            }
 
            std::wstring searchStr(searchText);
            std::transform(searchStr.begin(), searchStr.end(), searchStr.begin(), ::towlower);
 
            int foundIndex = -1;
            for (size_t i = 0; i < g_songList.size(); ++i) {
                std::wstring fileName = g_songList[i].fileName;
                std::transform(fileName.begin(), fileName.end(), fileName.begin(), ::towlower);
 
                if (fileName.find(searchStr) != std::wstring::npos) {
                    foundIndex = static_cast<int>(i);
                    break;
                }
            }
 
            if (foundIndex >= 0) {
                ListView_SetItemState(g_hwndSongList, foundIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
                ListView_EnsureVisible(g_hwndSongList, foundIndex, FALSE);
 
                WCHAR prompt[LARGE_BUFFER_SIZE];
                swprintf_s(prompt, LARGE_BUFFER_SIZE, L"找到歌曲: %s\n是否立即播放?", g_songList[foundIndex].fileName.c_str());
 
                if (MessageBoxW(hWnd, prompt, L"找到歌曲", MB_YESNO | MB_ICONQUESTION) == IDYES) {
                    StopPlayback();
                    g_currentSongIndex = foundIndex;
                    PlaySong(g_songList[foundIndex]);
                    g_isPlaying = TRUE;
                    SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
                }
            }
            else {
                MessageBoxW(hWnd, L"未找到匹配的歌曲", L"提示", MB_OK | MB_ICONINFORMATION);
            }
            break;
        }
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    case WM_NOTIFY:
    {
        // 处理ListView通知消息
        LPNMHDR pNMHDR = (LPNMHDR)lParam;
        if (pNMHDR->hwndFrom == g_hwndSongList && pNMHDR->code == NM_DBLCLK) {
            // 双击列表项
            LPNMITEMACTIVATE pNMIA = (LPNMITEMACTIVATE)lParam;
            if (pNMIA->iItem != -1) {
                StopPlayback();
                g_currentSongIndex = pNMIA->iItem;
                PlaySong(g_songList[g_currentSongIndex]);
                g_isPlaying = TRUE;
                SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
                UpdateSongListUI();
            }
        }
        break;
    }
    case WM_USER_PLAY_COMPLETE:
    {
        g_isPlaying = FALSE;
        SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"&#9654;");
 
        if (g_songList.empty()) {
            SetWindowText(g_hwndTitleLabel, L"播放列表为空");
            break;
        }
 
        int nextIndex = GetNextIndex();
        if (nextIndex < 0 || nextIndex >= (int)g_songList.size()) {
            nextIndex = 0;
        }
 
        StopPlayback();
        if (PathFileExistsW(g_songList[nextIndex].filePath.c_str())) {
            g_currentSongIndex = nextIndex;
            PlaySong(g_songList[nextIndex]);
            g_isPlaying = TRUE;
            SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
            UpdateSongListUI();
        }
        else {
            SetWindowText(g_hwndTitleLabel, L"下一首文件不存在");
        }
    }
    break;
    case WM_HSCROLL:
    {
        HWND hSlider = (HWND)lParam;
        // 1. 音量滑块
        if (hSlider == GetDlgItem(hWnd, IDC_VOLUME_SLIDER))
        {
            LRESULT volume = SendMessage(hSlider, TBM_GETPOS, 0, 0);
            SetVolume((int)volume);
            break;
        }
        // 进度条拖动结束:从拖动位置继续播放
        else if (hSlider == g_hwndProgressBar)
        {
            int code = LOWORD(wParam);
            if (code == TB_ENDTRACK)
            {
                // 1. 计算目标跳转时间(秒)—— 保留原有逻辑
                int pos = (int)SendMessage(g_hwndProgressBar, TBM_GETPOS, 0, 0);
                double seekTime = (double)pos / 1000.0 * g_totalDuration;
                if (seekTime < 0) seekTime = 0;
                if (seekTime > g_totalDuration) seekTime = g_totalDuration;
                // 2. 停止当前解码与播放 —— 保留原有逻辑
                StopPlayback();
                // 3. 重新打开文件 —— 保留原有逻辑(仅替换后续执行体)
                if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size())
                {
                    const SongInfo& song = g_songList[g_currentSongIndex];
                    double duration = 0.0; // 用于接收OpenAudioFile返回的总时长
                    if (OpenAudioFile(song.filePath, duration))
                    {
                        // 【修复1:将打开文件返回的总时长同步到全局变量】
                        if (duration > 0.0) {
                            g_totalDuration = duration;
                        }
 
                        // 【修复2:更稳妥的跳转逻辑(优先按音频流TimeBase计算,兜底兼容)】
                        if (fmt_ctx && audio_stream_idx >= 0 && seekTime > 0.0)
                        {
                            AVStream* st = fmt_ctx->streams[audio_stream_idx];
                            if (st && dec_ctx) {
                                // 按音频流的时间基准计算目标位置(更精准)
                                int64_t seek_pts = (int64_t)(seekTime / av_q2d(st->time_base));
                                avcodec_flush_buffers(dec_ctx); // 清空解码器缓冲(避免旧数据残留)
                                av_seek_frame(fmt_ctx, audio_stream_idx, seek_pts, AVSEEK_FLAG_BACKWARD);
                            }
                            else if (fmt_ctx)
                            {
                                // 兜底方案:按全局时间基准跳转(兼容旧逻辑)
                                int64_t seek_ts = (int64_t)(seekTime * AV_TIME_BASE);
                                avformat_seek_file(fmt_ctx, -1, INT64_MIN, seek_ts, INT64_MAX, 0);
                            }
                        }
 
                        // 【修复3:同步跳转后的播放状态】
                        g_playedDuration = seekTime; // 已播放时长设为跳转目标
                        g_lastUpdateTime = GetCurrentTimeSeconds(); // 重置进度更新计时起点
 
                        // 【修复4:重启解码线程与播放流程】
                        g_stopDecode = false;
                        is_playing_ffmpeg = true;
                        g_decodeThread = std::thread(DecodeThreadFunc);
 
                        // 【修复5:恢复UI与播放状态】
                        g_isPlaying = TRUE;
                        SetWindowText(GetDlgItem(g_hwndMain, IDC_PLAY_BUTTON), L"||"); // 按钮切为“暂停”
                        SetTimer(g_hwndMain, PROGRESS_TIMER_ID, PROGRESS_UPDATE_INTERVAL, NULL); // 重启进度更新
                        UpdateProgressDisplay(); // 立即刷新进度条与时间显示
                    }
                }
            }
        }
        break;
    }
    case WM_TIMER:
    {
        if (wParam == PROGRESS_TIMER_ID)  // 进度更新专用定时器
        {
            UpdateProgressDisplay();
        }
        break;
    }
    case WM_USER_UPDATE_PROGRESS:
    {
        UpdateProgressDisplay();
        break;
    }
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);
    }
    break;
    case WM_DESTROY:
        SavePlaylist();  // 确保窗口销毁时保存
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
 
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;
 
    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}
 
// 获取当前时间(秒)- 用于进度计算
double GetCurrentTimeSeconds()
{
    return (double)timeGetTime() / 1000.0;
}
 
// 格式化时长为分:秒格式
std::wstring FormatDuration(double seconds)
{
    int totalSeconds = static_cast<int>(seconds);
    int minutes = totalSeconds / 60;
    int secs = totalSeconds % 60;
 
    WCHAR buf[SMALL_BUFFER_SIZE];
    swprintf_s(buf, SMALL_BUFFER_SIZE, L"%02d:%02d", minutes, secs);
    return buf;
}
// 在全局变量区域添加以下变量用于优化
double g_lastUpdatedTime = -1.0;  // 记录上次更新标题栏的时间
std::wstring g_lastTitleText;     // 缓存上次标题文本
 
// 修改后的UpdateProgressDisplay函数
void UpdateProgressDisplay()
{
    if (!g_hwndMain || !g_hwndProgressBar || !g_hwndProgressText)
        return;
 
    // 防止拖动时自动更新滑块
    if (GetKeyState(VK_LBUTTON) < 0)
        return;
 
    // 仅在播放中且总时长有效时更新
    if (is_playing_ffmpeg && g_totalDuration > 0.0)
    {
        // 计算自上次更新以来的时间差,并更新已播放时长
        double currentTime = GetCurrentTimeSeconds();
        double timeDiff = currentTime - g_lastUpdateTime;
 
        if (timeDiff > 0)
        {
            // 使用原子操作安全地更新已播放时长
            double oldValue = g_playedDuration;
            double newValue = oldValue + (timeDiff * g_playbackSpeed);
 
            // 确保不会超过总时长
            if (newValue > g_totalDuration)
                newValue = g_totalDuration;
 
            g_playedDuration = newValue;
            g_lastUpdateTime = currentTime;
        }
 
        // 1. 进度条更新(保持高频但轻量)
        int pos = (int)((g_playedDuration / g_totalDuration) * 1000);
        SendMessage(g_hwndProgressBar, TBM_SETPOS, TRUE, pos);
 
        // 2. 进度文本更新(仅当时间变化时更新)
        std::wstring currTime = FormatDuration(g_playedDuration);
        std::wstring totalTime = FormatDuration(g_totalDuration);
 
        // 只在时间显示变化时才更新文本(减少80%的更新次数)
        static std::wstring lastTimeText;
        WCHAR timeText[SMALL_BUFFER_SIZE];
        swprintf_s(timeText, SMALL_BUFFER_SIZE, L"%s / %s", currTime.c_str(), totalTime.c_str());
 
        if (timeText != lastTimeText)
        {
            SetWindowText(g_hwndProgressText, timeText);
            lastTimeText = timeText;
        }
 
        // 3. 标题栏更新(关键优化:降低更新频率)
        if (g_hwndTitleLabel)
        {
            // 仅每秒更新一次标题栏(大幅减少重绘)
            if (g_playedDuration - g_lastUpdatedTime >= 1.0 || g_lastUpdatedTime < 0)
            {
                g_lastUpdatedTime = g_playedDuration;
 
                WCHAR originalTitle[LARGE_BUFFER_SIZE];
                GetWindowText(g_hwndTitleLabel, originalTitle, LARGE_BUFFER_SIZE);
 
                // 移除之前的时间信息
                WCHAR* pipePos = wcschr(originalTitle, L'|');
                std::wstring baseTitle = (pipePos) ? std::wstring(originalTitle, pipePos) : std::wstring(originalTitle);
 
                // 构建新标题
                WCHAR fullTitle[LARGE_BUFFER_SIZE];
                swprintf_s(fullTitle, LARGE_BUFFER_SIZE, L"%s | %s / %s",
                    baseTitle.c_str(), currTime.c_str(), totalTime.c_str());
 
                // 仅在内容变化时更新(避免相同文本重复设置)
                if (fullTitle != g_lastTitleText)
                {
                    SetWindowText(g_hwndTitleLabel, fullTitle);
                    g_lastTitleText = fullTitle;
                }
            }
        }
    }
}
 
void UpdateModeButtonText()
{
    if (!g_hwndModeButton) return;
 
    const WCHAR* modeText = L"";
    switch (g_currentMode)
    {
    case PlayMode::AllLoop:
        modeText = L"全部循环";
        break;
    case PlayMode::SingleLoop:
        modeText = L"单曲循环";
        break;
    case PlayMode::Random:
        modeText = L"随机播放";
        break;
    }
 
    SetWindowText(g_hwndModeButton, modeText);
}
 
int GetNextIndex()
{
    if (g_songList.empty()) return -1;
    int count = (int)g_songList.size();
    if (count == 1) return 0;
 
    switch (g_currentMode)
    {
    case PlayMode::SingleLoop:
        return g_currentSongIndex;
 
    case PlayMode::Random:
    {
        std::uniform_int_distribution<int> dist(0, count - 1);
        int idx = dist(g_randEngine);
        int tries = 0;
        while (idx == g_currentSongIndex && tries++ < 10) {
            idx = dist(g_randEngine);
        }
        return idx;
    }
 
    case PlayMode::AllLoop:
    default:
        if (g_currentSongIndex < 0) return 0;
        return (g_currentSongIndex + 1) % count;
    }
}
 
int GetPrevIndex()
{
    if (g_songList.empty()) return -1;
    int count = (int)g_songList.size();
    if (count == 1) return 0;
 
    switch (g_currentMode)
    {
    case PlayMode::SingleLoop:
        return g_currentSongIndex;
 
    case PlayMode::Random:
    {
        std::uniform_int_distribution<int> dist(0, count - 1);
        int idx = dist(g_randEngine);
        int tries = 0;
        while (idx == g_currentSongIndex && tries++ < 10) {
            idx = dist(g_randEngine);
        }
        return idx;
    }
 
    case PlayMode::AllLoop:
    default:
        if (g_currentSongIndex <= 0) return count - 1;
        return (g_currentSongIndex - 1 + count) % count;
    }
}
 
void InitFFmpeg()
{
    avformat_network_init();
}
 
std::string WstringToUtf8(const std::wstring& wstr)
{
    if (wstr.empty()) return "";
 
    int size_needed = WideCharToMultiByte(CP_UTF8, 0,
        wstr.data(), (int)wstr.size(), NULL, 0, NULL, NULL);
    if (size_needed == 0) return "";
 
    std::string str(size_needed, 0);
    WideCharToMultiByte(CP_UTF8, 0,
        wstr.data(), (int)wstr.size(), &str[0], size_needed, NULL, NULL);
 
    return str;
}
 
bool OpenAudioFile(const std::wstring& filePath, double& duration)
{
    CloseAudioFile(); // 先确保旧资源已释放
 
    std::string utf8_path = WstringToUtf8(filePath);
    if (utf8_path.empty()) return false;
 
    if (avformat_open_input(&fmt_ctx, utf8_path.c_str(), NULL, NULL) < 0) {
        SetWindowText(g_hwndTitleLabel, L"错误:无法打开音频文件");
        return false;
    }
 
    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
        SetWindowText(g_hwndTitleLabel, L"错误:无法获取流信息");
        avformat_close_input(&fmt_ctx);
        fmt_ctx = NULL;
        return false;
    }
 
    audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    duration = 0.0;
 
    if (audio_stream_idx >= 0) {
        AVStream* audio_stream = fmt_ctx->streams[audio_stream_idx];
        if (audio_stream->duration != AV_NOPTS_VALUE) {
            duration = (double)audio_stream->duration * av_q2d(audio_stream->time_base);
        }
        else if (fmt_ctx->duration != AV_NOPTS_VALUE) {
            duration = (double)fmt_ctx->duration / AV_TIME_BASE;
        }
    }
 
    if (audio_stream_idx < 0) {
        SetWindowText(g_hwndTitleLabel, L"错误:文件中无音频流");
        avformat_close_input(&fmt_ctx);
        fmt_ctx = NULL;
        return false;
    }
 
    AVCodecParameters* codec_par = fmt_ctx->streams[audio_stream_idx]->codecpar;
    const AVCodec* codec = avcodec_find_decoder(codec_par->codec_id);
    if (!codec) {
        SetWindowText(g_hwndTitleLabel, L"错误:不支持的音频编码");
        avformat_close_input(&fmt_ctx);
        fmt_ctx = NULL;
        return false;
    }
 
    dec_ctx = avcodec_alloc_context3(codec);
    if (!dec_ctx) {
        SetWindowText(g_hwndTitleLabel, L"错误:无法分配解码器上下文");
        avformat_close_input(&fmt_ctx);
        fmt_ctx = NULL;
        return false;
    }
 
    if (avcodec_parameters_to_context(dec_ctx, codec_par) < 0) {
        SetWindowText(g_hwndTitleLabel, L"错误:无法复制解码器参数");
        avcodec_free_context(&dec_ctx);
        dec_ctx = NULL;
        avformat_close_input(&fmt_ctx);
        fmt_ctx = NULL;
        return false;
    }
 
    if (avcodec_open2(dec_ctx, codec, NULL) < 0) {
        SetWindowText(g_hwndTitleLabel, L"错误:无法打开解码器");
        avcodec_free_context(&dec_ctx);
        dec_ctx = NULL;
        avformat_close_input(&fmt_ctx);
        fmt_ctx = NULL;
        return false;
    }
 
    swr_ctx = swr_alloc_set_opts(
        NULL,
        av_get_default_channel_layout(dec_ctx->channels),  // 目标声道布局(与原始一致)
        dec_ctx->sample_fmt,                               // 目标采样格式(与原始一致)
        dec_ctx->sample_rate,                              // 目标采样率(与原始一致)
        av_get_default_channel_layout(dec_ctx->channels),  // 原始声道布局
        dec_ctx->sample_fmt,                               // 原始采样格式
        dec_ctx->sample_rate,                              // 原始采样率
        0, NULL
    );
 
    // 关键修复:初始化SwrContext并检查错误
    if (!swr_ctx || swr_init(swr_ctx) < 0) {
        SetWindowText(g_hwndTitleLabel, L"错误:重采样上下文初始化失败");
        // 释放资源
        swr_free(&swr_ctx);
        avcodec_free_context(&dec_ctx);
        avformat_close_input(&fmt_ctx);
        return false;
    }
 
 
    pkt = av_packet_alloc();
    frame = av_frame_alloc();
    if (!pkt || !frame) {
        SetWindowText(g_hwndTitleLabel, L"错误:无法分配缓存");
        if (pkt) {
            av_packet_free(&pkt);
            pkt = NULL;
        }
        if (frame) {
            av_frame_free(&frame);
            frame = NULL;
        }
        if (swr_ctx) {
            swr_free(&swr_ctx);
            swr_ctx = NULL;
        }
        avcodec_free_context(&dec_ctx);
        dec_ctx = NULL;
        avformat_close_input(&fmt_ctx);
        fmt_ctx = NULL;
        return false;
    }
 
    // 1. 定义WaveOut支持的目标格式(16位整数PCM)
    AVSampleFormat target_sample_fmt = AV_SAMPLE_FMT_S16; // 16位整数(WaveOut必支持)
    int target_channels = dec_ctx->channels;              // 保留原始声道数
    int target_sample_rate = dec_ctx->sample_rate;        // 保留原始采样率
 
    // 2. 重新配置SwrContext(输入:原始格式,输出:16位PCM)
    swr_ctx = swr_alloc_set_opts(
        NULL,
        av_get_default_channel_layout(target_channels),  // 目标声道布局
        target_sample_fmt,                               // 目标采样格式(16位整数)
        target_sample_rate,                              // 目标采样率
        av_get_default_channel_layout(dec_ctx->channels),// 原始声道布局
        dec_ctx->sample_fmt,                             // 原始采样格式
        dec_ctx->sample_rate,                             // 原始采样率
        0, NULL
    );
 
    // 3. 初始化SwrContext
    if (!swr_ctx || swr_init(swr_ctx) < 0) {
        SetWindowText(g_hwndTitleLabel, L"错误:重采样上下文初始化失败");
        swr_free(&swr_ctx);
        avcodec_free_context(&dec_ctx);
        avformat_close_input(&fmt_ctx);
        return false;
    }
 
    // 4. 配置WAVEFORMATEX(匹配16位PCM格式)
    WAVEFORMATEX wfx = { 0 };
    wfx.wFormatTag = WAVE_FORMAT_PCM;                    // 标准PCM格式
    wfx.nChannels = target_channels;                     // 原始声道数
    wfx.nSamplesPerSec = target_sample_rate;             // 原始采样率
    wfx.wBitsPerSample = av_get_bytes_per_sample(target_sample_fmt) * 8; // 16位
    wfx.nBlockAlign = (wfx.wBitsPerSample / 8) * wfx.nChannels; // 块对齐(必须正确)
    wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; // 每秒字节数
    wfx.cbSize = 0; // 标准PCM格式,cbSize设为0
 
    // 5. 检查waveOutOpen是否成功(添加错误日志)
    MMRESULT waveResult = waveOutOpen(
        &hWaveOut,
        WAVE_MAPPER,
        &wfx,
        (DWORD_PTR)WaveOutProc,
        (DWORD_PTR)NULL,
        CALLBACK_FUNCTION
    );
    if (waveResult != MMSYSERR_NOERROR) {
        WCHAR errMsg[256];
        swprintf_s(errMsg, L"错误:无法打开音频设备(代码:%d)", waveResult);
        SetWindowText(g_hwndTitleLabel, errMsg);
        CloseAudioFile();
        return false;
    }
 
 
    g_buffers.clear();
    g_buffers.resize(N_BUFFERS);
    {
        std::lock_guard<std::mutex> lk(g_bufMutex);
        while (!g_freeBuffers.empty()) g_freeBuffers.pop();
        for (int i = 0; i < N_BUFFERS; ++i) {
            g_buffers[i].data.clear();
            g_buffers[i].hdr = WAVEHDR();
            g_buffers[i].prepared = false;
            g_buffers[i].duration = 0.0;
            g_freeBuffers.push(i);
        }
    }
 
    return true;
}
 
void CloseAudioFile()
{
    // 1. 停止解码线程
    g_stopDecode = true;
    g_bufCv.notify_all();
    if (g_decodeThread.joinable()) {
        g_decodeThread.join();
    }
 
    // 2. 重置音频设备与缓冲区
    if (hWaveOut) {
        waveOutReset(hWaveOut);
 
        std::lock_guard<std::mutex> lk(g_bufMutex);
        for (auto& buf : g_buffers) {
            if (buf.prepared) {
                waveOutUnprepareHeader(hWaveOut, &buf.hdr, sizeof(WAVEHDR));
                buf.prepared = false;
            }
            buf.data.clear();
            buf.hdr = {};
            buf.duration = 0.0;
            g_freeBuffers.push(&buf - &g_buffers[0]);
        }
    }
 
    // 3. 释放FFmpeg资源
    if (swr_ctx) {
        swr_free(&swr_ctx);
        swr_ctx = NULL;
    }
    if (dec_ctx) {
        avcodec_free_context(&dec_ctx);
        dec_ctx = NULL;
    }
    if (fmt_ctx) {
        avformat_close_input(&fmt_ctx);
        fmt_ctx = NULL;
    }
    if (pkt) {
        av_packet_free(&pkt);
        pkt = NULL;
    }
    if (frame) {
        av_frame_free(&frame);
        frame = NULL;
    }
 
    hWaveOut = NULL;
 
    // 重置播放状态
    g_totalDuration = 0.0;
    g_playedDuration = 0.0;
    is_playing_ffmpeg = false;
    g_isPlaying = FALSE;
}
 
bool DecodeAudioFrame(std::vector<uint8_t>& pcm, double& frameDuration)
{
    pcm.clear();
    frameDuration = 0.0;
    int ret = 0;
 
    if (!fmt_ctx || !pkt || !dec_ctx || !frame || !swr_ctx) {
        OutputDebugString(L"DecodeAudioFrame:FFmpeg核心指针为空,终止解码\n");
        return false;
    }
 
    while (true) {
        ret = av_read_frame(fmt_ctx, pkt);
        if (ret < 0) {
            if (ret == AVERROR_EOF) return false;
 
            OutputDebugString(L"警告:读取数据包失败,重试...\n");
            if (pkt) av_packet_unref(pkt);
            Sleep(10);
            continue;
        }
 
        if (!pkt) continue;
 
        if (pkt->stream_index != audio_stream_idx) {
            av_packet_unref(pkt);
            continue;
        }
 
        ret = avcodec_send_packet(dec_ctx, pkt);
        if (ret < 0) {
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                av_packet_unref(pkt);
                continue;
            }
            else {
                OutputDebugString(L"警告:发送数据包失败,跳过...\n");
                av_packet_unref(pkt);
                continue;
            }
        }
 
        ret = avcodec_receive_frame(dec_ctx, frame);
        av_packet_unref(pkt);
 
        if (ret == AVERROR(EAGAIN)) {
            continue;
        }
        else if (ret == AVERROR_EOF) {
            return false;
        }
        else if (ret < 0) {
            OutputDebugString(L"警告:接收帧失败,跳过...\n");
            continue;
        }
 
        // 计算当前帧的时长(秒)
        frameDuration = (double)frame->nb_samples / dec_ctx->sample_rate;
 
        // 适配原始采样率:用目标采样率(原始采样率)替换硬编码的44100
        int64_t delay = swr_get_delay(swr_ctx, frame->sample_rate);
 
        // 关键修改:用原始采样率(目标采样率)替换44100LL,保持与重采样器配置一致
        int64_t out_nb_samples_64 = av_rescale_rnd(
            delay + frame->nb_samples,
            (int64_t)dec_ctx->sample_rate,  // 目标采样率=原始采样率(与重采样器匹配)
            frame->sample_rate,
            AV_ROUND_UP
        );
 
        if (out_nb_samples_64 > INT_MAX) {
            av_frame_unref(frame);
            return false;
        }
 
        int out_nb_samples = (int)out_nb_samples_64;
 
        uint8_t* out_buf[2] = { NULL };
        int out_linesize;
        if (av_samples_alloc(out_buf, &out_linesize, 2, out_nb_samples, AV_SAMPLE_FMT_S16, 0) < 0 || !out_buf[0]) {
            av_frame_unref(frame);
            return false;
        }
 
        int out_nb_samples_gen = swr_convert(
            swr_ctx, out_buf, out_nb_samples,
            (const uint8_t**)frame->data, frame->nb_samples
        );
        // 输出重采样后的样本数(判断是否成功生成数据)
        WCHAR swrLog[256];
        swprintf_s(swrLog, L"swr_convert 输出样本数: %d(负数表示失败)\n", out_nb_samples_gen);
        OutputDebugStringW(swrLog);
 
        // 若重采样失败,添加详细错误日志
        if (out_nb_samples_gen < 0) {
            swprintf_s(swrLog, L"重采样失败!错误码: %d\n", out_nb_samples_gen);
            OutputDebugStringW(swrLog);
        }
        // 1. 获取目标格式的参数(16位PCM,已在OpenAudioFile中定义)
        AVSampleFormat target_sample_fmt = AV_SAMPLE_FMT_S16;
        int target_channels = dec_ctx->channels;
 
        // 2. 动态计算PCM数据大小(样本数 × 声道数 × 每个样本的字节数)
        int bytes_per_sample = av_get_bytes_per_sample(target_sample_fmt); // 16位=2字节
        int out_size = out_nb_samples_gen * target_channels * bytes_per_sample;
 
        // 3. 拷贝数据(确保大小匹配)
        if (out_nb_samples_gen > 0 && out_buf[0] != nullptr && out_size > 0) {
            pcm.resize(out_size);
            memcpy(pcm.data(), out_buf[0], out_size);
        }
 
        av_freep(&out_buf[0]);
        av_frame_unref(frame);
        // 输出函数最终返回状态(成功/失败)
        WCHAR retLog[256];
        if (!pcm.empty()) {
            swprintf_s(retLog, L"DecodeAudioFrame 成功,返回PCM数据大小: %d 字节\n", (int)pcm.size());
        }
        else {
            swprintf_s(retLog, L"DecodeAudioFrame 失败,返回空数据\n");
        }
        OutputDebugStringW(retLog);
        return !pcm.empty();
    }
}
 
void PlayPCMData(const std::vector<uint8_t>& pcm, double duration, int bufIndex)
{
    std::lock_guard<std::mutex> lock(g_bufMutex);
 
    if (bufIndex < 0 || bufIndex >= (int)g_buffers.size()) return;
 
    auto& buffer = g_buffers[bufIndex];
    buffer.data = pcm; // 直接使用原始数据
    buffer.duration = duration;
 
    // 准备WAVEHDR
    buffer.hdr.lpData = (LPSTR)buffer.data.data();
    buffer.hdr.dwBufferLength = (DWORD)buffer.data.size();
    buffer.hdr.dwFlags = 0;
 
    waveOutPrepareHeader(hWaveOut, &buffer.hdr, sizeof(WAVEHDR));
    waveOutWrite(hWaveOut, &buffer.hdr, sizeof(WAVEHDR));
    buffer.prepared = true;
}
 
 
void PlaySong(const SongInfo& song)
{
    StopPlayback();
 
    double duration = 0.0;
    if (!OpenAudioFile(song.filePath, duration)) {
        OutputDebugString(L"错误:打开音频文件失败\n");
        CloseAudioFile();
        return;
    }
 
    if (!hWaveOut || !fmt_ctx || !dec_ctx) {
        OutputDebugString(L"错误:音频组件初始化失败\n");
        CloseAudioFile();
        return;
    }
 
    // 初始化进度相关变量
    g_totalDuration = duration;
    g_playedDuration = 0.0;
    g_lastUpdateTime = GetCurrentTimeSeconds();
    g_playbackSpeed = 1.0;
 
    g_stopDecode = false;
    is_playing_ffmpeg = true;
    g_decodeThread = std::thread(DecodeThreadFunc);
 
    // 提取文件名和文件夹路径并拼接显示
    WCHAR fileName[MAX_PATH] = { 0 };
    wcscpy_s(fileName, MAX_PATH, song.fileName.c_str());
 
    WCHAR folderPath[MAX_PATH] = { 0 };
    wcscpy_s(folderPath, MAX_PATH, song.filePath.c_str());
    PathRemoveFileSpecW(folderPath);
 
    WCHAR fullTitle[LARGE_BUFFER_SIZE] = { 0 };
    swprintf_s(fullTitle, LARGE_BUFFER_SIZE, L"%s (%s) - %s - %s", fileName, folderPath, song.fileSize.c_str(), song.bitrate.c_str());
 
    SetWindowText(g_hwndTitleLabel, fullTitle);
 
    // 初始化进度显示
    UpdateProgressDisplay();
 
    // 启动进度条更新定时器
    SetTimer(g_hwndMain, PROGRESS_TIMER_ID, PROGRESS_UPDATE_INTERVAL, NULL);
}
 
void StopPlayback()
{
    is_playing_ffmpeg = false;
    g_stopDecode = true;
    g_bufCv.notify_all();
    if (g_decodeThread.joinable()) {
        g_decodeThread.join();
    }
    CloseAudioFile();
 
    // 停止播放时,销毁进度条更新定时器并重置进度条
    KillTimer(g_hwndMain, PROGRESS_TIMER_ID);
    SendMessage(g_hwndProgressBar, TBM_SETPOS, TRUE, 0);
    SetWindowText(g_hwndProgressText, L"00:00 / 00:00");
 
    SetWindowText(g_hwndTitleLabel, L"歌曲播放中");
}
 
void PausePlayback()
{
    if (hWaveOut) {
        waveOutPause(hWaveOut);
        is_playing_ffmpeg = false;
 
        // 暂停时计算当前已播放时间
        double currentTime = GetCurrentTimeSeconds();
        double timeDiff = currentTime - g_lastUpdateTime;
 
        if (timeDiff > 0)
        {
            g_playedDuration += timeDiff * g_playbackSpeed;
            g_lastUpdateTime = currentTime;
        }
 
        // 暂停时销毁定时器
        KillTimer(g_hwndMain, PROGRESS_TIMER_ID);
 
        // 暂停时更新进度显示
        UpdateProgressDisplay();
 
        // 暂停时显示详细信息
        if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size()) {
            const SongInfo& song = g_songList[g_currentSongIndex];
            WCHAR fileName[MAX_PATH] = { 0 };
            wcscpy_s(fileName, song.fileName.c_str());
            WCHAR folderPath[MAX_PATH] = { 0 };
            wcscpy_s(folderPath, song.filePath.c_str());
            PathRemoveFileSpecW(folderPath);
 
            // 显示当前进度
            std::wstring currTime = FormatDuration(g_playedDuration);
            std::wstring totalTime = FormatDuration(g_totalDuration);
 
            WCHAR fullTitle[LARGE_BUFFER_SIZE] = { 0 };
            swprintf_s(fullTitle, LARGE_BUFFER_SIZE, L"已暂停 - %s (%s) | %s / %s",
                fileName, folderPath, currTime.c_str(), totalTime.c_str());
            SetWindowText(g_hwndTitleLabel, fullTitle);
        }
        else {
            SetWindowText(g_hwndTitleLabel, L"已暂停 - 请选择歌曲播放");
        }
    }
}
 
void ResumePlayback()
{
    if (hWaveOut) {
        waveOutRestart(hWaveOut);
        is_playing_ffmpeg = true;
 
        // 更新上次更新时间为当前时间
        g_lastUpdateTime = GetCurrentTimeSeconds();
 
        // 继续播放时重启定时器
        SetTimer(g_hwndMain, PROGRESS_TIMER_ID, PROGRESS_UPDATE_INTERVAL, NULL);
 
        if (!g_decodeThread.joinable()) {
            g_stopDecode = false;
            g_decodeThread = std::thread(DecodeThreadFunc);
        }
 
        // 继续播放时显示完整信息
        if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size()) {
            const SongInfo& song = g_songList[g_currentSongIndex];
            WCHAR fileName[MAX_PATH] = { 0 };
            wcscpy_s(fileName, song.fileName.c_str());
            WCHAR folderPath[MAX_PATH] = { 0 };
            wcscpy_s(folderPath, song.filePath.c_str());
            PathRemoveFileSpecW(folderPath);
 
            WCHAR fullTitle[LARGE_BUFFER_SIZE] = { 0 };
            swprintf_s(fullTitle, LARGE_BUFFER_SIZE, L"%s (%s) - %s - %s", fileName, folderPath, song.fileSize.c_str(), song.bitrate.c_str());
            SetWindowText(g_hwndTitleLabel, fullTitle);
        }
    }
    else {
        if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size()) {
            PlaySong(g_songList[g_currentSongIndex]);
        }
    }
}
 
void DecodeThreadFunc()
{
    std::vector<uint8_t> pcm;
    double frameDuration = 0.0;
    bool isFirstFrame = true;
    bool decode_finished = false;
 
    while (!g_stopDecode && !decode_finished) {
        if (!is_playing_ffmpeg) {
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
            continue;
        }
 
        if (g_totalDuration > 0.0 && g_playedDuration >= g_totalDuration)
        {
            decode_finished = true;
            break;
        }
 
        pcm.clear();
        frameDuration = 0.0;
        bool decodeSuccess = DecodeAudioFrame(pcm, frameDuration);
        if (!decodeSuccess) {
            decode_finished = true;
            break;
        }
 
        while (pcm.size() < MIN_BUFFER_SIZE && pcm.size() < MAX_BUFFER_SIZE && !g_stopDecode && is_playing_ffmpeg) {
            std::vector<uint8_t> nextPcm;
            double nextDuration = 0.0;
            if (!DecodeAudioFrame(nextPcm, nextDuration) || nextPcm.empty()) break;
            if (pcm.size() + nextPcm.size() > MAX_BUFFER_SIZE) break;
            pcm.insert(pcm.end(), nextPcm.begin(), nextPcm.end());
            frameDuration += nextDuration;
        }
 
        if (pcm.empty()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            continue;
        }
 
        int freeBufIdx = -1;
        {
            std::unique_lock<std::mutex> lk(g_bufMutex);
            g_bufCv.wait(lk, [] {
                return g_stopDecode || !is_playing_ffmpeg || !g_freeBuffers.empty();
                });
 
            if (g_stopDecode || !is_playing_ffmpeg) break;
            if (g_freeBuffers.empty()) continue;
 
            freeBufIdx = g_freeBuffers.front();
            g_freeBuffers.pop();
        }
 
        PCMBuffer& freeBuf = g_buffers[freeBufIdx];
        freeBuf.data = pcm;
        freeBuf.duration = frameDuration;
        freeBuf.hdr.lpData = (LPSTR)freeBuf.data.data();
        freeBuf.hdr.dwBufferLength = (DWORD)freeBuf.data.size();
        freeBuf.hdr.dwFlags = 0;
        freeBuf.hdr.dwLoops = 0;
 
        if (!freeBuf.prepared) {
            if (waveOutPrepareHeader(hWaveOut, &freeBuf.hdr, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
                std::lock_guard<std::mutex> lk(g_bufMutex);
                g_freeBuffers.push(freeBufIdx);
                OutputDebugString(L"错误:缓冲区准备失败\n");
                std::this_thread::sleep_for(std::chrono::milliseconds(50));
                continue;
            }
            freeBuf.prepared = true;
        }
 
        if (waveOutWrite(hWaveOut, &freeBuf.hdr, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
            std::lock_guard<std::mutex> lk(g_bufMutex);
            g_freeBuffers.push(freeBufIdx);
            OutputDebugString(L"错误:音频数据写入失败\n");
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
            continue;
        }
 
        if (isFirstFrame) {
            OutputDebugString(L"首帧播放成功,持续解码中...\n");
            isFirstFrame = false;
        }
    }
 
    // 等待播放完成
    while (!g_stopDecode && g_playedDuration < g_totalDuration)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
 
    if (g_hwndMain && !g_stopDecode) {
        PostMessage(g_hwndMain, WM_USER_PLAY_COMPLETE, 0, 0);
    }
 
    is_playing_ffmpeg = false;
    g_stopDecode = true;
    std::lock_guard<std::mutex> lk(g_bufMutex);
    g_bufCv.notify_all();
    OutputDebugString(L"解码线程正常退出\n");
}
 
void AddFiles(HWND hWnd)
{
    OPENFILENAMEW ofn;
    WCHAR szFile[MAX_PATH * 20] = { 0 };
 
    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hWnd;
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = sizeof(szFile) / sizeof(WCHAR);
    ofn.lpstrFilter = L"音频文件 (*.mp3;*.wav;*.wma;*.flac;*.m4a;*.ogg)\0*.mp3;*.wav;*.wma;*.flac;*.m4a;*.ogg\0所有文件 (*.*)\0*.*\0";
    ofn.nFilterIndex = 1;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER;
 
    if (GetOpenFileNameW(&ofn) == TRUE) {
        WCHAR* p = szFile;
        WCHAR path[MAX_PATH] = { 0 };
        wcscpy_s(path, MAX_PATH, p);
        p += wcslen(p) + 1;
 
        bool added = false;
 
        if (*p == 0) {
            // 单个文件
            SongInfo song;
            song.filePath = path;
            song.fileName = PathFindFileNameW(path);
 
            // 获取文件大小
            int64_t sizeBytes;
            song.fileSize = GetFileSizeString(path, sizeBytes);
            song.sizeBytes = sizeBytes;
 
            // 获取比特率
            int bitrateKbps;
            song.bitrate = GetBitrateString(path, bitrateKbps);
            song.bitrateKbps = bitrateKbps;
 
            // 获取时长
            std::string utf8_path = WstringToUtf8(path);
            AVFormatContext* fmt_ctx = NULL;
            if (avformat_open_input(&fmt_ctx, utf8_path.c_str(), NULL, NULL) == 0) {
                avformat_find_stream_info(fmt_ctx, NULL);
                int audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
                if (audio_stream_idx >= 0) {
                    AVStream* audio_stream = fmt_ctx->streams[audio_stream_idx];
                    if (audio_stream->duration != AV_NOPTS_VALUE) {
                        song.duration = (double)audio_stream->duration * av_q2d(audio_stream->time_base);
                    }
                    else if (fmt_ctx->duration != AV_NOPTS_VALUE) {
                        song.duration = (double)fmt_ctx->duration / AV_TIME_BASE;
                    }
                }
                avformat_close_input(&fmt_ctx);
            }
 
            // 检查是否已存在
            bool exists = false;
            for (const auto& s : g_songList) {
                if (s.filePath == song.filePath) {
                    exists = true;
                    break;
                }
            }
 
            if (!exists) {
                g_songList.push_back(song);
                added = true;
            }
        }
        else {
            // 多个文件
            while (*p) {
                WCHAR fullPath[MAX_PATH] = { 0 };
                wcscpy_s(fullPath, MAX_PATH, path);
                wcscat_s(fullPath, MAX_PATH, L"\\");
                wcscat_s(fullPath, MAX_PATH, p);
 
                SongInfo song;
                song.filePath = fullPath;
                song.fileName = PathFindFileNameW(fullPath);
 
                // 获取文件大小
                int64_t sizeBytes;
                song.fileSize = GetFileSizeString(fullPath, sizeBytes);
                song.sizeBytes = sizeBytes;
 
                // 获取比特率
                int bitrateKbps;
                song.bitrate = GetBitrateString(fullPath, bitrateKbps);
                song.bitrateKbps = bitrateKbps;
 
                // 获取时长
                std::string utf8_path = WstringToUtf8(fullPath);
                AVFormatContext* fmt_ctx = NULL;
                if (avformat_open_input(&fmt_ctx, utf8_path.c_str(), NULL, NULL) == 0) {
                    avformat_find_stream_info(fmt_ctx, NULL);
                    int audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
                    if (audio_stream_idx >= 0) {
                        AVStream* audio_stream = fmt_ctx->streams[audio_stream_idx];
                        if (audio_stream->duration != AV_NOPTS_VALUE) {
                            song.duration = (double)audio_stream->duration * av_q2d(audio_stream->time_base);
                        }
                        else if (fmt_ctx->duration != AV_NOPTS_VALUE) {
                            song.duration = (double)fmt_ctx->duration / AV_TIME_BASE;
                        }
                    }
                    avformat_close_input(&fmt_ctx);
                }
 
                // 检查是否已存在
                bool exists = false;
                for (const auto& s : g_songList) {
                    if (s.filePath == song.filePath) {
                        exists = true;
                        break;
                    }
                }
 
                if (!exists) {
                    g_songList.push_back(song);
                    added = true;
                }
 
                p += wcslen(p) + 1;
            }
        }
 
        if (added) {
            UpdateSongListUI();
            SavePlaylist();
        }
    }
}
 
void AddFolder(HWND hWnd)
{
    WCHAR szFolder[MAX_PATH] = { 0 };
 
    BROWSEINFOW bi = { 0 };
    bi.hwndOwner = hWnd;
    bi.pszDisplayName = szFolder;
    bi.lpszTitle = L"选择音频文件夹";
    bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
 
    LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
    if (pidl != NULL) {
        if (SHGetPathFromIDListW(pidl, szFolder)) {
            EnumMusicFiles(szFolder);
            UpdateSongListUI();
            SavePlaylist();
        }
        CoTaskMemFree(pidl);
    }
}
 
void ClearSongList()
{
    g_songList.clear();
    StopPlayback();
    SetWindowText(g_hwndTitleLabel, L"播放列表已清空");
}
 
void EnumMusicFiles(const std::wstring& folder)
{
    std::wstring searchPath = folder + L"\\*.*";
 
    WIN32_FIND_DATAW findData;
    HANDLE hFind = FindFirstFileW(searchPath.c_str(), &findData);
 
    if (hFind == INVALID_HANDLE_VALUE) return;
 
    do {
        if (wcscmp(findData.cFileName, L".") == 0 || wcscmp(findData.cFileName, L"..") == 0)
            continue;
 
        std::wstring fullPath = folder + L"\\" + findData.cFileName;
 
        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            EnumMusicFiles(fullPath);  // 递归处理子文件夹
        }
        else {
            WCHAR* ext = PathFindExtensionW(findData.cFileName);
            if (ext != NULL) {
                if (_wcsicmp(ext, L".mp3") == 0 || _wcsicmp(ext, L".wav") == 0 ||
                    _wcsicmp(ext, L".wma") == 0 || _wcsicmp(ext, L".flac") == 0 ||
                    _wcsicmp(ext, L".m4a") == 0 || _wcsicmp(ext, L".ogg") == 0) {
 
                    // 检查是否已存在
                    bool exists = false;
                    for (const auto& song : g_songList) {
                        if (song.filePath == fullPath) {
                            exists = true;
                            break;
                        }
                    }
 
                    if (!exists) {
                        SongInfo song;
                        song.filePath = fullPath;
                        song.fileName = PathFindFileNameW(fullPath.c_str());
 
                        // 获取文件大小
                        int64_t sizeBytes;
                        song.fileSize = GetFileSizeString(fullPath, sizeBytes);
                        song.sizeBytes = sizeBytes;
 
                        // 获取比特率
                        int bitrateKbps;
                        song.bitrate = GetBitrateString(fullPath, bitrateKbps);
                        song.bitrateKbps = bitrateKbps;
 
                        // 获取时长
                        std::string utf8_path = WstringToUtf8(fullPath);
                        AVFormatContext* fmt_ctx = NULL;
                        if (avformat_open_input(&fmt_ctx, utf8_path.c_str(), NULL, NULL) == 0) {
                            avformat_find_stream_info(fmt_ctx, NULL);
                            int audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
                            if (audio_stream_idx >= 0) {
                                AVStream* audio_stream = fmt_ctx->streams[audio_stream_idx];
                                if (audio_stream->duration != AV_NOPTS_VALUE) {
                                    song.duration = (double)audio_stream->duration * av_q2d(audio_stream->time_base);
                                }
                                else if (fmt_ctx->duration != AV_NOPTS_VALUE) {
                                    song.duration = (double)fmt_ctx->duration / AV_TIME_BASE;
                                }
                            }
                            avformat_close_input(&fmt_ctx);
                        }
 
                        g_songList.push_back(song);
                    }
                }
            }
        }
    } while (FindNextFileW(hFind, &findData) != 0);
 
    FindClose(hFind);
}
 
void UpdateSongListUI()
{
    if (g_hwndSongList == NULL) return;
 
    // 清空列表
    ListView_DeleteAllItems(g_hwndSongList);
 
    // 添加所有歌曲
    for (size_t i = 0; i < g_songList.size(); ++i) {
        const SongInfo& song = g_songList[i];
 
        // 添加列表项(第一列)
        LVITEM lvi = { 0 };
        lvi.mask = LVIF_TEXT;
        lvi.iItem = (int)i;
        lvi.iSubItem = 0;
        lvi.pszText = const_cast<LPWSTR>(song.fileName.c_str());
        ListView_InsertItem(g_hwndSongList, &lvi);
 
        // 设置第二列(文件大小)
        ListView_SetItemText(g_hwndSongList, (int)i, 1, const_cast<LPWSTR>(song.fileSize.c_str()));
 
        // 设置第三列(比特率)
        ListView_SetItemText(g_hwndSongList, (int)i, 2, const_cast<LPWSTR>(song.bitrate.c_str()));
 
        // 设置第四列(时长)
        std::wstring durationStr = FormatDuration(song.duration);
        ListView_SetItemText(g_hwndSongList, (int)i, 3, const_cast<LPWSTR>(durationStr.c_str()));
    }
 
    // 选中当前播放的歌曲
    if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size()) {
        ListView_SetItemState(g_hwndSongList, g_currentSongIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
        ListView_EnsureVisible(g_hwndSongList, g_currentSongIndex, FALSE);
    }
}
 
// -------------------------- CSV存储核心函数 --------------------------
// 辅助函数:宽字符串转UTF-8,并处理CSV特殊字符(双引号、逗号)的转义
std::string WstringToUtf8Escaped(const std::wstring& wstr) {
    std::string utf8 = WstringToUtf8(wstr);
    std::string escaped;
    for (char c : utf8) {
        if (c == '"') {
            escaped += "\"\""; // 双引号转义为两个双引号
        }
        else if (c == ',') {
            escaped += c;
        }
        else {
            escaped += c;
        }
    }
    return escaped;
}
 
// 辅助函数:UTF-8字符串转宽字符串
std::wstring Utf8ToWstring(const std::string& utf8) {
    if (utf8.empty()) return L"";
    int size_needed = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), (int)utf8.size(), NULL, 0);
    if (size_needed == 0) return L"";
    std::wstring wstr(size_needed, 0);
    MultiByteToWideChar(CP_UTF8, 0, utf8.data(), (int)utf8.size(), &wstr[0], size_needed);
    return wstr;
}
 
// 辅助函数:解析CSV行,正确分割被双引号包裹的字段
std::vector<std::string> SplitCsvLine(const std::string& line) {
    std::vector<std::string> fields;
    std::string current_field;
    bool in_quotes = false;
 
    for (char c : line) {
        if (c == '"') {
            // 处理双引号转义("" -> ")
            if (in_quotes && !current_field.empty() && current_field.back() == '"') {
                current_field.pop_back();
                in_quotes = false;
            }
            else {
                in_quotes = !in_quotes;
            }
        }
        else if (c == ',' && !in_quotes) {
            // 仅在未被引号包裹时,逗号才作为分隔符
            fields.push_back(current_field);
            current_field.clear();
        }
        else {
            current_field += c;
        }
    }
    // 添加最后一个字段
    if (!current_field.empty()) {
        fields.push_back(current_field);
    }
    return fields;
}
 
// 保存播放列表(CSV格式,批量写入)
void SavePlaylist() {
    WCHAR appDataPath[MAX_PATH] = { 0 };
    if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, appDataPath) != S_OK) {
        OutputDebugString(L"错误:无法获取 APPDATA 路径\n");
        return;
    }
 
    // 1. 创建存储目录
    std::wstring folderPath = appDataPath;
    folderPath += L"\\MiniMusicPlayer";
    if (!CreateDirectoryW(folderPath.c_str(), NULL)) {
        if (GetLastError() != ERROR_ALREADY_EXISTS) {
            OutputDebugString(L"错误:无法创建 MiniMusicPlayer 文件夹\n");
            return;
        }
    }
 
    // 2. 定义配置文件和播放列表文件路径(CSV格式)
    std::wstring configPath = folderPath + L"\\config.csv";   // 存当前索引、播放模式
    std::wstring playlistPath = folderPath + L"\\playlist.csv"; // 存歌曲列表
 
 
    // -------------------------- 保存配置信息(currentIndex, playMode)--------------------------
    std::string configData;
    configData += std::to_string(g_currentSongIndex);
    configData += ",";
    configData += std::to_string(static_cast<int>(g_currentMode));
 
    // 一次性写入配置文件
    HANDLE hConfigFile = CreateFileW(configPath.c_str(), GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hConfigFile != INVALID_HANDLE_VALUE) {
        DWORD bytesWritten;
        WriteFile(hConfigFile, configData.data(), (DWORD)configData.size(), &bytesWritten, NULL);
        CloseHandle(hConfigFile);
    }
    else {
        OutputDebugString(L"错误:无法创建 config.csv\n");
        return;
    }
 
 
    // -------------------------- 保存歌曲列表(批量拼接+一次性写入)--------------------------
    if (g_songList.empty()) {
        // 若列表为空,创建空文件
        HANDLE hEmptyFile = CreateFileW(playlistPath.c_str(), GENERIC_WRITE, 0, NULL,
            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hEmptyFile != INVALID_HANDLE_VALUE) CloseHandle(hEmptyFile);
        OutputDebugString(L"播放列表已保存(空列表)\n");
        return;
    }
 
    // 拼接所有歌曲数据到内存缓冲区
    std::string playlistData;
    for (const auto& song : g_songList) {
        // 字段1:filePath(转UTF-8+转义,用双引号包裹)
        std::string filePath = WstringToUtf8Escaped(song.filePath);
        playlistData += "\"" + filePath + "\"";
        playlistData += ",";
 
        // 字段2:fileName
        std::string fileName = WstringToUtf8Escaped(song.fileName);
        playlistData += "\"" + fileName + "\"";
        playlistData += ",";
 
        // 字段3:sizeBytes
        playlistData += std::to_string(song.sizeBytes);
        playlistData += ",";
 
        // 字段4:fileSize
        std::string fileSize = WstringToUtf8Escaped(song.fileSize);
        playlistData += "\"" + fileSize + "\"";
        playlistData += ",";
 
        // 字段5:bitrateKbps
        playlistData += std::to_string(song.bitrateKbps);
        playlistData += ",";
 
        // 字段6:bitrate
        std::string bitrate = WstringToUtf8Escaped(song.bitrate);
        playlistData += "\"" + bitrate + "\"";
        playlistData += ",";
 
        // 字段7:duration
        char durationBuf[32];
        sprintf_s(durationBuf, "%.6f", song.duration);
        playlistData += durationBuf;
 
        // 换行分隔不同歌曲
        playlistData += "\n";
    }
 
    // 一次性写入播放列表文件
    HANDLE hPlaylistFile = CreateFileW(playlistPath.c_str(), GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hPlaylistFile != INVALID_HANDLE_VALUE) {
        DWORD bytesWritten;
        WriteFile(hPlaylistFile, playlistData.data(), (DWORD)playlistData.size(), &bytesWritten, NULL);
        CloseHandle(hPlaylistFile);
    }
    else {
        OutputDebugString(L"错误:无法创建 playlist.csv\n");
        return;
    }
 
    OutputDebugString(L"播放列表已保存(CSV格式)\n");
}
 
// 加载播放列表(CSV格式,快速解析)
void LoadPlaylist() {
    WCHAR appDataPath[MAX_PATH] = { 0 };
    if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, appDataPath) != S_OK) {
        OutputDebugString(L"错误:无法获取 APPDATA 路径\n");
        return;
    }
 
    // 1. 定义文件路径
    std::wstring folderPath = appDataPath;
    folderPath += L"\\MiniMusicPlayer";
    std::wstring configPath = folderPath + L"\\config.csv";
    std::wstring playlistPath = folderPath + L"\\playlist.csv";
 
 
    // -------------------------- 加载配置信息(currentIndex, playMode)--------------------------
    HANDLE hConfigFile = CreateFileW(configPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hConfigFile != INVALID_HANDLE_VALUE) {
        char configBuf[1024] = { 0 };
        DWORD bytesRead;
        if (ReadFile(hConfigFile, configBuf, sizeof(configBuf) - 1, &bytesRead, NULL)) {
            std::string configLine(configBuf);
            std::vector<std::string> configFields = SplitCsvLine(configLine);
            // 解析当前播放索引
            if (configFields.size() >= 1) {
                try {
                    g_currentSongIndex = std::stoi(configFields[0]);
                }
                catch (...) {
                    g_currentSongIndex = -1;
                }
            }
            // 解析播放模式
            if (configFields.size() >= 2) {
                try {
                    int mode = std::stoi(configFields[1]);
                    if (mode >= 0 && mode <= 2) {
                        g_currentMode = static_cast<PlayMode>(mode);
                    }
                }
                catch (...) {
                    g_currentMode = PlayMode::AllLoop;
                }
            }
        }
        CloseHandle(hConfigFile);
    }
    else {
        // 配置文件不存在,用默认值
        g_currentSongIndex = -1;
        g_currentMode = PlayMode::AllLoop;
        OutputDebugString(L"警告:未找到 config.csv,使用默认配置\n");
    }
 
 
    // -------------------------- 加载歌曲列表(批量读取+逐行解析)--------------------------
    HANDLE hPlaylistFile = CreateFileW(playlistPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hPlaylistFile == INVALID_HANDLE_VALUE) {
        OutputDebugString(L"警告:未找到 playlist.csv(可能是第一次运行)\n");
        return;
    }
 
    // 1. 一次性读取所有列表数据到内存
    DWORD fileSize = GetFileSize(hPlaylistFile, NULL);
    if (fileSize == INVALID_FILE_SIZE) {
        CloseHandle(hPlaylistFile);
        OutputDebugString(L"错误:无法获取 playlist.csv 大小\n");
        return;
    }
    std::string playlistData(fileSize, 0);
    DWORD bytesRead;
    if (!ReadFile(hPlaylistFile, &playlistData[0], fileSize, &bytesRead, NULL)) {
        CloseHandle(hPlaylistFile);
        OutputDebugString(L"错误:无法读取 playlist.csv\n");
        return;
    }
    CloseHandle(hPlaylistFile);
 
 
    // 2. 逐行解析CSV数据
    g_songList.clear();
    std::stringstream ss(playlistData);
    std::string line;
 
    while (std::getline(ss, line)) {
        if (line.empty()) continue;
 
        // 分割当前行的字段
        std::vector<std::string> fields = SplitCsvLine(line);
        if (fields.size() < 7) continue;
 
        SongInfo song;
        try {
            // 解析字段(UTF-8转宽字符)
            song.filePath = Utf8ToWstring(fields[0]);
            song.fileName = Utf8ToWstring(fields[1]);
            song.sizeBytes = std::stoll(fields[2]);
            song.fileSize = Utf8ToWstring(fields[3]);
            song.bitrateKbps = std::stoi(fields[4]);
            song.bitrate = Utf8ToWstring(fields[5]);
            song.duration = std::stod(fields[6]);
 
            // 检查文件是否存在
            if (PathFileExistsW(song.filePath.c_str())) {
                g_songList.push_back(song);
            }
        }
        catch (...) {
            // 解析失败,跳过当前歌曲
            continue;
        }
    }
 
    // 3. 修正当前播放索引(避免越界)
    if (g_currentSongIndex < 0 || g_currentSongIndex >= (int)g_songList.size()) {
        g_currentSongIndex = -1;
    }
 
    UpdateSongListUI();
    OutputDebugString(L"播放列表已加载(CSV格式)\n");
}
 
void SetVolume(int volume)
{
    if (hWaveOut == NULL) return;
 
    DWORD vol = (DWORD)(volume * 65535.0 / 100.0);
    vol = MAKELONG(vol, vol);
    waveOutSetVolume(hWaveOut, vol);
}
 
void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
    if (uMsg == WOM_DONE) {
        WAVEHDR* pHdr = (WAVEHDR*)dwParam1;
        if (!pHdr) return;
 
        // 遍历查找匹配的缓冲区
        int targetBufIdx = -1;
        for (int i = 0; i < (int)g_buffers.size(); ++i) {
            if (&g_buffers[i].hdr == pHdr) {
                targetBufIdx = i;
                break;
            }
        }
        if (targetBufIdx == -1 || !g_buffers[targetBufIdx].prepared) {
            OutputDebugString(L"WaveOutProc:未找到匹配的缓冲区或缓冲区未准备\n");
            return;
        }
 
        // 处理目标缓冲区
        PCMBuffer& targetBuf = g_buffers[targetBufIdx];
        g_playedDuration += targetBuf.duration;
        if (g_playedDuration > g_totalDuration) {
            g_playedDuration = g_totalDuration;
        }
 
        // 回收缓冲区(线程安全)
        std::lock_guard<std::mutex> lk(g_bufMutex);
        bool isInFree = false;
        std::queue<int> temp = g_freeBuffers;
        while (!temp.empty()) {
            if (temp.front() == targetBufIdx) { isInFree = true; break; }
            temp.pop();
        }
        if (!isInFree) {
            g_freeBuffers.push(targetBufIdx);
        }
        g_bufCv.notify_one();
 
        // 触发进度更新
        if (g_hwndMain) {
            PostMessage(g_hwndMain, WM_USER_UPDATE_PROGRESS, 0, 0);
        }
    }
}
 
std::wstring GetFileSizeString(const std::wstring& filePath, int64_t& sizeBytes)
{
    HANDLE hFile = CreateFileW(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        sizeBytes = 0;
        return L"未知";
    }
 
    LARGE_INTEGER fileSize;
    if (!GetFileSizeEx(hFile, &fileSize)) {
        CloseHandle(hFile);
        sizeBytes = 0;
        return L"未知";
    }
    CloseHandle(hFile);
 
    sizeBytes = fileSize.QuadPart;
 
    // 格式化文件大小
    if (sizeBytes >= 1024 * 1024 * 1024) {
        double sizeGB = (double)sizeBytes / (1024 * 1024 * 1024);
        WCHAR buf[SMALL_BUFFER_SIZE];
        swprintf_s(buf, SMALL_BUFFER_SIZE, L"%.2f GB", sizeGB);
        return buf;
    }
    else if (sizeBytes >= 1024 * 1024) {
        double sizeMB = (double)sizeBytes / (1024 * 1024);
        WCHAR buf[SMALL_BUFFER_SIZE];
        swprintf_s(buf, SMALL_BUFFER_SIZE, L"%.2f MB", sizeMB);
        return buf;
    }
    else if (sizeBytes >= 1024) {
        double sizeKB = (double)sizeBytes / 1024;
        WCHAR buf[SMALL_BUFFER_SIZE];
        swprintf_s(buf, SMALL_BUFFER_SIZE, L"%.2f KB", sizeKB);
        return buf;
    }
    else {
        WCHAR buf[SMALL_BUFFER_SIZE];
        swprintf_s(buf, SMALL_BUFFER_SIZE, L"%lld B", sizeBytes);
        return buf;
    }
}
 
std::wstring GetBitrateString(const std::wstring& filePath, int& bitrateKbps)
{
    std::string utf8_path = WstringToUtf8(filePath);
    if (utf8_path.empty()) {
        bitrateKbps = 0;
        return L"未知";
    }
 
    AVFormatContext* fmt_ctx = NULL;
    if (avformat_open_input(&fmt_ctx, utf8_path.c_str(), NULL, NULL) < 0) {
        bitrateKbps = 0;
        return L"未知";
    }
 
    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
        avformat_close_input(&fmt_ctx);
        bitrateKbps = 0;
        return L"未知";
    }
 
    int audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    int bitrate = 0;
 
    if (audio_stream_idx >= 0) {
        AVStream* audio_stream = fmt_ctx->streams[audio_stream_idx];
        if (audio_stream->codecpar->bit_rate > 0) {
            bitrate = audio_stream->codecpar->bit_rate;
        }
        else if (fmt_ctx->bit_rate > 0) {
            bitrate = fmt_ctx->bit_rate;
        }
    }
 
    avformat_close_input(&fmt_ctx);
 
    bitrateKbps = bitrate / 1000;  // 转换为kbps
 
    if (bitrateKbps > 0) {
        WCHAR buf[SMALL_BUFFER_SIZE];
        swprintf_s(buf, SMALL_BUFFER_SIZE, L"%d kbps", bitrateKbps);
        return buf;
    }
    else {
        return L"未知";
    }
}
 

附件

后退
顶部 底部