diff --git a/MusicPlayer2/Common.cpp b/MusicPlayer2/Common.cpp index 67661a71f..4126a71b2 100644 --- a/MusicPlayer2/Common.cpp +++ b/MusicPlayer2/Common.cpp @@ -928,12 +928,18 @@ void CCommon::WriteLog(const wchar_t* path, const wstring& content) cur_time.wHour, cur_time.wMinute, cur_time.wSecond, cur_time.wMilliseconds); ofstream out_put{ path, std::ios::app }; out_put << buff << CCommon::UnicodeToStr(content, CodeType::UTF8_NO_BOM) << std::endl; + out_put.close(); // 这里需要显式关闭以避免被不同线程连续调用时丢失内容(不过还是不能承受并发,多线程并发时请自行加锁 } -wstring CCommon::DisposeCmdLineFiles(const wstring& cmd_line, vector& files) +void CCommon::DisposeCmdLineFiles(const wstring& cmd_line, vector& files) { + // 解析命令行参数中的文件/文件夹路径放入files + // files 中能够接受音频文件路径/播放列表文件路径/文件夹路径随意乱序出现 + // files 中无法被识别为“播放列表文件路径”“文件夹路径”的项目会被直接加入默认播放列表 + // TODO: 这里可能需要添加以下功能,我没有其他windows版本的经验,不确定这里怎样改 + // 文件/文件夹存在判断;路径通配符展开;相对路径转换绝对路径;支持不在同一文件夹下的多个文件路径 files.clear(); - if (cmd_line.empty()) return wstring(); + if (cmd_line.empty()) return; wstring path; //先找出字符串中的文件夹路径,从命令行参数传递过来的文件肯定都是同一个文件夹下的 if (cmd_line[0] == L'\"') //如果第一个文件用双引号包含起来 @@ -951,13 +957,7 @@ wstring CCommon::DisposeCmdLineFiles(const wstring& cmd_line, vector& f files.push_back(cmd_line.substr(0, index1)); } int path_size = path.size(); - if (path_size < 2) return wstring(); - if (IsFolder(files[0])) - //if (files[0].size() > 4 && files[0][files[0].size() - 4] != L'.' && files[0][files[0].size() - 5] != L'.') - { - //如果第1个文件不是文件而是文件夹,则返直接回该文件夹的路径 - return files[0]; - } + if (path_size < 2) return; int index{}; while (true) { @@ -974,7 +974,7 @@ wstring CCommon::DisposeCmdLineFiles(const wstring& cmd_line, vector& f files.push_back(cmd_line.substr(index, index1 - index)); } } - return wstring(); + return; //CString out_info; //out_info += _T("命令行参数:"); //out_info += cmd_line.c_str(); diff --git a/MusicPlayer2/Common.h b/MusicPlayer2/Common.h index bb82ce51d..30535f5f7 100644 --- a/MusicPlayer2/Common.h +++ b/MusicPlayer2/Common.h @@ -263,8 +263,8 @@ class CCommon //写入日志 static void WriteLog(const wchar_t* path, const wstring& content); - //将通过命令行参数传递过来的多个文件路径拆分,并保存到file容器里,如果参数传递过来的第一个文件不是文件而是文件夹,则返回文件夹路径,否则,返回空字符串 - static wstring DisposeCmdLineFiles(const wstring& cmd_line, vector& files); + // 将通过命令行参数传递过来的多个文件路径拆分,并保存到file容器里 + static void DisposeCmdLineFiles(const wstring& cmd_line, vector& files); //解析命令行参数中的命令 static bool GetCmdLineCommand(const wstring& cmd_line, int& command); diff --git a/MusicPlayer2/Define.h b/MusicPlayer2/Define.h index 5a116635c..bb32a85b5 100644 --- a/MusicPlayer2/Define.h +++ b/MusicPlayer2/Define.h @@ -87,6 +87,7 @@ using _tstring = std::string; #define TIMER_1_SEC 1237 #define TIMER_DESKTOP_LYRIC 1238 #define TIMER_DESKTOP_LYRIC_2 1239 +#define TIMER_CMD_OPEN_FILES_DELAY 1240 #define UI_INTERVAL_DEFAULT 50 //定义界面刷新时间的默认时间间隔(毫秒) #define MIN_UI_INTERVAL 10 //界面刷新时间间隔最小值 diff --git a/MusicPlayer2/MusicPlayerDlg.cpp b/MusicPlayer2/MusicPlayerDlg.cpp index 53cc1b751..b2abb0b4c 100644 --- a/MusicPlayer2/MusicPlayerDlg.cpp +++ b/MusicPlayer2/MusicPlayerDlg.cpp @@ -2417,6 +2417,7 @@ void CMusicPlayerDlg::OnTimer(UINT_PTR nIDEvent) { // TODO: 在此添加消息处理程序代码和/或调用默认值 + static bool cmd_open_files_ing{ false }; // 用于阻止TIMER_CMD_OPEN_FILES_DELAY重入 //响应主定时器 if (nIDEvent == TIMER_ID) { @@ -2454,20 +2455,35 @@ void CMusicPlayerDlg::OnTimer(UINT_PTR nIDEvent) } else //从命令行参数获取要打开的文件 { + bool open_default_playlist{ true }; vector files; - wstring path = CCommon::DisposeCmdLineFiles(m_cmdLine, files); - if (!path.empty()) + CCommon::DisposeCmdLineFiles(m_cmdLine, files); + if (!files.empty()) { - CPlayer::GetInstance().CreateWithPath(path); + if (CPlaylistFile::IsPlaylistFile(files.front())) + { + CPlayer::GetInstance().CreateWithPlaylist(files.front()); + files.erase(files.begin()); + open_default_playlist = false; + } + else if (CCommon::IsFolder(files.front())) + { + CPlayer::GetInstance().CreateWithPath(files.front()); + files.erase(files.begin()); + open_default_playlist = false; + } } - else + if (open_default_playlist) { - if (!files.empty() && CPlaylistFile::IsPlaylistFile(files[0])) - CPlayer::GetInstance().CreateWithPlaylist(files[0]); - else - CPlayer::GetInstance().CreateWithFiles(files); + CPlayer::GetInstance().CreateWithPlaylist(theApp.m_playlist_dir + DEFAULT_PLAYLIST_NAME); + } + if (!files.empty()) + { + std::unique_lock lock(m_cmd_open_files_mutx); + // theApp.WriteLog(m_cmdLine + L""); + m_cmd_open_files.insert(m_cmd_open_files.begin(), files.begin(), files.end()); // 当前实例成功创建互斥量,故插入到开头 + SetTimer(TIMER_CMD_OPEN_FILES_DELAY, 1000, nullptr); } - //MessageBox(m_cmdLine.c_str(), NULL, MB_ICONINFORMATION); } DrawInfo(); m_uiThread = AfxBeginThread(UiThreadFunc, (LPVOID)&m_ui_thread_para); @@ -2668,6 +2684,81 @@ void CMusicPlayerDlg::OnTimer(UINT_PTR nIDEvent) cur_ui->ResetVolumeToPlayTime(); } + // 距最后一次设置此定时器1s,说明已经1s没有收到copy_data消息,将m_cmd_open_files内容设为当前播放 + else if (nIDEvent == TIMER_CMD_OPEN_FILES_DELAY && !cmd_open_files_ing) + { + cmd_open_files_ing = true; + // 这里会在一次一次的回调中先逐个打开并移除m_cmd_open_files中的播放列表/文件夹条目 + // m_cmd_open_files中不含播放列表/文件夹后将剩余歌曲一次在默认播放列表打开并清空m_cmd_open_files + // m_cmd_open_files为空后再KillTimer + wstring path_playlist, path_folder, path_playlist_new; + vector path_songs; + m_cmd_open_files_mutx.lock(); + auto iter_p = std::find_if(m_cmd_open_files.begin(), m_cmd_open_files.end(), + [&](const wstring& path) { return CPlaylistFile::IsPlaylistFile(path); }); + if (iter_p != m_cmd_open_files.end()) + path_playlist = *iter_p; + else + { + auto iter_f = std::find_if(m_cmd_open_files.begin(), m_cmd_open_files.end(), + [&](const wstring& path) { return CCommon::IsFolder(path); }); + if (iter_f != m_cmd_open_files.end()) + path_folder = *iter_f; + else + path_songs = m_cmd_open_files; + } + m_cmd_open_files_mutx.unlock(); + // CPlayer的初始化方法会向主线程发消息而主线程的copy_data有可能正在等待获取这个锁故需要先解锁,否则有可能死锁 + if (!path_playlist.empty()) + { + path_playlist_new = path_playlist; + if (CPlayer::GetInstance().OpenPlaylistFile(path_playlist_new)) // 注意OpenPlaylistFile会修改参数,需将修改结果反映到m_cmd_open_files,防止反复复制播放列表 + path_playlist_new.clear(); // 下面的行为是path_playlist_new为空直接移除path_playlist,否则将m_cmd_open_files中的path_playlist替换为path_playlist_new + } + else if (!path_folder.empty()) + { + if (!CPlayer::GetInstance().OpenFolder(path_folder)) + path_folder.clear(); + } + else + { + if (!CPlayer::GetInstance().OpenFilesInDefaultPlaylist(path_songs)) + path_songs.clear(); + } + m_cmd_open_files_mutx.lock(); + if (!path_playlist.empty()) + { + auto iter = std::find(m_cmd_open_files.begin(), m_cmd_open_files.end(), path_playlist); + if (iter != m_cmd_open_files.end()) + { + if (!path_playlist_new.empty()) + *iter = path_playlist_new; + else + m_cmd_open_files.erase(iter); + } + } + else if (!path_folder.empty()) + { + auto iter = std::find(m_cmd_open_files.begin(), m_cmd_open_files.end(), path_folder); + if (iter != m_cmd_open_files.end()) + { + m_cmd_open_files.erase(iter); + } + } + else if (!path_songs.empty()) + { + // 因为中间解锁过所以m_cmd_open_files和path_songs不一定相同,不能直接清空m_cmd_open_files + auto new_end = std::remove_if(m_cmd_open_files.begin(), m_cmd_open_files.end(), + [&](const wstring& path) { return CCommon::IsItemInVector(path_songs, path); }); + m_cmd_open_files.erase(new_end, m_cmd_open_files.end()); + } + // 如果m_cmd_open_files已全部处理完成则关闭定时器,否则下次时间到再尝试 + if (m_cmd_open_files.empty()) + KillTimer(TIMER_CMD_OPEN_FILES_DELAY); + m_cmd_open_files_mutx.unlock(); + cmd_open_files_ing = false; + } + CMainDialogBase::OnTimer(nIDEvent); } @@ -6112,19 +6203,12 @@ BOOL CMusicPlayerDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct) if (cmd_line.empty()) return 0; vector files; - CCommon::DisposeCmdLineFiles(wstring(cmd_line), files); - if (pCopyDataStruct->dwData == COPY_DATA_OPEN_FILE) - { - if (!files.empty() && CPlaylistFile::IsPlaylistFile(files[0])) - CPlayer::GetInstance().OpenPlaylistFile(files[0]); - else - CPlayer::GetInstance().OpenFilesInDefaultPlaylist(files); - } - else if (pCopyDataStruct->dwData == COPY_DATA_ADD_FILE) - { - if (CPlayer::GetInstance().IsPlaylistMode()) - CPlayer::GetInstance().AddFilesToPlaylist(files, true); - } + CCommon::DisposeCmdLineFiles(cmd_line, files); + // 这里不再区分COPY_DATA_OPEN_FILE和COPY_DATA_ADD_FILE,统一处理 + std::unique_lock lock(m_cmd_open_files_mutx); + // theApp.WriteLog(cmd_line + L""); + m_cmd_open_files.insert(m_cmd_open_files.end(), files.begin(),files.end()); // 将来自其他实例的cmd追加到末尾 + SetTimer(TIMER_CMD_OPEN_FILES_DELAY, 1000, nullptr); } } diff --git a/MusicPlayer2/MusicPlayerDlg.h b/MusicPlayer2/MusicPlayerDlg.h index 96e93d581..3330e8c5d 100644 --- a/MusicPlayer2/MusicPlayerDlg.h +++ b/MusicPlayer2/MusicPlayerDlg.h @@ -91,6 +91,8 @@ class CMusicPlayerDlg : public CMainDialogBase HACCEL m_hAccel{}; wstring m_cmdLine; //命令行参数 + std::mutex m_cmd_open_files_mutx; // 保护m_cmd_open_files的线程同步对象 + vector m_cmd_open_files; // 来自命令行/copy_data的待打开文件队列 CDC* m_pUiDC; //当前窗口的DC std::vector> m_ui_list; //保存每个界面类的指针 diff --git a/MusicPlayer2/Player.cpp b/MusicPlayer2/Player.cpp index de55dfd96..6dbc8ff02 100644 --- a/MusicPlayer2/Player.cpp +++ b/MusicPlayer2/Player.cpp @@ -169,7 +169,8 @@ void CPlayer::CreateWithPlaylist(const wstring& playlist_path) LoadConfig(); LoadRecentPath(); LoadRecentPlaylist(); - OpenPlaylistFile(playlist_path); + wstring tmp{ playlist_path }; + OpenPlaylistFile(tmp); SetTitle(); m_controls.Init(); } @@ -972,7 +973,6 @@ void CPlayer::LoopPlaylist(int& song_track) void CPlayer::ChangePath(const wstring& path, int track, bool play, int position) { - MusicControl(Command::CLOSE); m_path = path; if (m_path.empty() || (m_path.back() != L'/' && m_path.back() != L'\\')) //如果输入的新路径为空或末尾没有斜杠,则在末尾加上一个 m_path.append(1, L'\\'); @@ -983,8 +983,6 @@ void CPlayer::ChangePath(const wstring& path, int track, bool play, int position m_current_position.fromInt(position); SaveConfig(); SetTitle(); - //MusicControl(Command::OPEN); - //IniLyrics(); } void CPlayer::SetPath(const PathInfo& path_info) @@ -994,6 +992,8 @@ void CPlayer::SetPath(const PathInfo& path_info) m_loading = true; IniPlayerCore(); + MusicControl(Command::CLOSE); + if (GetSongNum() > 0) { SaveCurrentPlaylist(); @@ -1016,10 +1016,10 @@ void CPlayer::SetPath(const PathInfo& path_info) } -void CPlayer::SetPlaylist(const wstring& playlist_path, int track, int position, bool init, bool play, bool force) +bool CPlayer::SetPlaylist(const wstring& playlist_path, int track, int position, bool init, bool play, bool force) { - if (m_loading) return; - if (!GetPlayStatusMutex().try_lock_for(std::chrono::milliseconds(1000))) return; + if (m_loading) return false; + if (!GetPlayStatusMutex().try_lock_for(std::chrono::milliseconds(1000))) return false; m_loading = true; IniPlayerCore(); @@ -1069,15 +1069,18 @@ void CPlayer::SetPlaylist(const wstring& playlist_path, int track, int position, EmplaceCurrentPlaylistToRecent(); IniPlayList(true, false, play); + return true; } -void CPlayer::OpenFolder(wstring path, bool contain_sub_folder, bool play) +bool CPlayer::OpenFolder(wstring path, bool contain_sub_folder, bool play) { - if (m_loading) return; - if (!GetPlayStatusMutex().try_lock_for(std::chrono::milliseconds(1000))) return; + if (m_loading) return false; + if (!GetPlayStatusMutex().try_lock_for(std::chrono::milliseconds(1000))) return false; m_loading = true; IniPlayerCore(); + MusicControl(Command::CLOSE); + if (path.empty() || (path.back() != L'/' && path.back() != L'\\')) //如果打开的新路径为空或末尾没有斜杠,则在末尾加上一个 path.append(1, L'\\'); bool path_exist{ false }; @@ -1115,21 +1118,22 @@ void CPlayer::OpenFolder(wstring path, bool contain_sub_folder, bool play) } EmplaceCurrentPathToRecent(); //保存打开的路径到最近路径 SaveRecentPath(); + return true; } -void CPlayer::OpenFilesInDefaultPlaylist(const vector& files, bool play) +bool CPlayer::OpenFilesInDefaultPlaylist(const vector& files, bool play) { vector songs(files.size()); for (size_t i{}; i < files.size(); ++i) songs[i].file_path = files[i]; - OpenSongsInDefaultPlaylist(songs, play); + return OpenSongsInDefaultPlaylist(songs, play); } -void CPlayer::OpenSongsInDefaultPlaylist(const vector& songs, bool play) +bool CPlayer::OpenSongsInDefaultPlaylist(const vector& songs, bool play) { - if (songs.empty()) return; - if (m_loading) return; - if (!GetPlayStatusMutex().try_lock_for(std::chrono::milliseconds(1000))) return; + if (songs.empty()) return false; + if (m_loading) return false; + if (!GetPlayStatusMutex().try_lock_for(std::chrono::milliseconds(1000))) return false; m_loading = true; IniPlayerCore(); @@ -1172,6 +1176,7 @@ void CPlayer::OpenSongsInDefaultPlaylist(const vector& songs, bool pla SetTitle(); //用当前正在播放的歌曲名作为窗口标题 IniPlayList(true, false, play); + return true; } void CPlayer::OpenSongsInTempPlaylist(const vector& songs, int play_index, bool play /*= true*/) @@ -1251,7 +1256,7 @@ void CPlayer::OpenASongInFolderMode(const SongInfo& song, bool play) IniPlayList(false, false, play); //根据新路径重新初始化播放列表 } -void CPlayer::OpenPlaylistFile(const wstring& file_path) +bool CPlayer::OpenPlaylistFile(wstring& file_path) { CFilePathHelper helper(file_path); if (!CCommon::StringCompareNoCase(helper.GetDir(), theApp.m_playlist_dir)) //如果打开的播放列表文件不是程序默认的播放列表目录,则将其转换为*.playlist格式并复制到默认的播放列表目录 @@ -1264,13 +1269,14 @@ void CPlayer::OpenPlaylistFile(const wstring& file_path) CPlaylistFile playlist; playlist.LoadFromFile(file_path); playlist.SaveToFile(new_path); + file_path = new_path; - SetPlaylist(new_path, 0, 0); + return SetPlaylist(new_path, 0, 0); } else //如果打开的播放文件就在默认播放列表目录下,则直接打开 { auto path_info = CPlaylistMgr::Instance().FindPlaylistInfo(file_path); - SetPlaylist(file_path, path_info.track, path_info.position); + return SetPlaylist(file_path, path_info.track, path_info.position); } } diff --git a/MusicPlayer2/Player.h b/MusicPlayer2/Player.h index 812fa6df8..4a27a3433 100644 --- a/MusicPlayer2/Player.h +++ b/MusicPlayer2/Player.h @@ -288,23 +288,25 @@ class CPlayer // 切换到指定路径的文件夹模式,没有PathInfo时应使用CPlayer::OpenFolder void SetPath(const PathInfo& path_info); // 切换到指定播放列表模式 - // force为true时忽略continue_when_switch_playlist设置播放track指定歌曲 - void SetPlaylist(const wstring& playlist_path, int track, int position, bool init = false, bool play = false, bool force = false); - // 切换到指定路径的播放列表模式/通过“打开文件夹”来设置路径的处理(不进行“切换播放列表时继续播放”) - void OpenFolder(wstring path, bool contain_sub_folder = false, bool play = false); + // force为true时忽略continue_when_switch_playlist设置播放track指定歌曲(没能取得播放状态锁返回false) + bool SetPlaylist(const wstring& playlist_path, int track, int position, bool init = false, bool play = false, bool force = false); + // 切换到指定路径的播放列表模式/通过“打开文件夹”来设置路径的处理 + // (不进行“切换播放列表时继续播放”)(没能取得播放状态锁返回false) + bool OpenFolder(wstring path, bool contain_sub_folder = false, bool play = false); - // 向默认播放列表添加并打开多个文件,play用来设置是否立即播放 + // 向默认播放列表添加并打开多个文件,play用来设置是否立即播放(没能取得播放状态锁返回false) // 由于cue解析问题,请在判断需要“添加歌曲”而不是“添加文件”时尽量使用CPlayer::OpenSongsInDefaultPlaylist代替此方法而不是使用path构建SongInfo - void OpenFilesInDefaultPlaylist(const vector& files, bool play = true); - // 向默认播放列表添加并打开多个歌曲,play用来设置是否立即播放 - void OpenSongsInDefaultPlaylist(const vector& songs, bool play = true); + bool OpenFilesInDefaultPlaylist(const vector& files, bool play = true); + // 向默认播放列表添加并打开多个歌曲,play用来设置是否立即播放(没能取得播放状态锁返回false) + bool OpenSongsInDefaultPlaylist(const vector& songs, bool play = true); // 打开多个歌曲并覆盖临时播放列表,play用来设置是否立即播放 void OpenSongsInTempPlaylist(const vector& songs, int play_index = 0, bool play = true); // 切换到此歌曲音频文件目录的文件夹模式并播放此歌曲 void OpenASongInFolderMode(const SongInfo& song, bool play = false); - // 打开一个播放列表文件(支持所有支持的播放列表格式,不在默认播放列表目录则以.playlist格式复制到默认播放列表目录) - void OpenPlaylistFile(const wstring& file_path); + // 打开一个播放列表文件(没能取得播放状态锁返回false) + // 支持所有支持的播放列表格式,不在默认播放列表目录则以.playlist格式复制到默认播放列表目录,会修改参数file_path为复制后的路径 + bool OpenPlaylistFile(wstring& file_path); // 向当前播放列表添加文件,仅在播放列表模式可用,如果一个都没有添加,则返回false,否则返回true // 由于cue解析问题,请在判断需要“添加歌曲”而不是“添加文件”时尽量使用CPlayer::AddSongs代替此方法而不是使用path构建SongInfo // files内含有cue原始文件时返回值可能不正确(处理在线程函数,无法及时返回是否添加,初始化线程结束后有保存操作,不必另外执行保存)