From bf1af609b292ce831a1355610b2831d57a5b8d7e Mon Sep 17 00:00:00 2001 From: casey langen Date: Tue, 28 Mar 2017 09:48:32 -0700 Subject: [PATCH] A massive overhaul to the CD Audio plugin: - Integrated with the new IIndexerSource, IIndexerWriter, and IIndexerNotifier SDK interfaces - Added a simple capabilities model to the playback infrastructure. This is used to detect whether or not streams can be prefetched. For example, CDDA cannot because of seek times while playing tracks. - Added bare-bones CddaDataModel that uses the win32 api to enumerate connected devices. This is used for disc fingerprinting (using the CDDB algorithm) and encapsulating data related to discs and tracks. Also, use the WM_DEVICECHANGE API to detect when new discs are inserted, or existing discs are removed. - Don't install the global hotkey hook when running in a debugger. - Added the ability for the user to configure seeking vs scrubbing when changing the playhead --- src/contrib/cddadecoder/CddaDataModel.cpp | 366 ++++++++++++++++++ src/contrib/cddadecoder/CddaDataModel.h | 139 +++++++ src/contrib/cddadecoder/CddaDataStream.cpp | 131 +++++-- src/contrib/cddadecoder/CddaDataStream.h | 12 +- .../cddadecoder/CddaDataStreamFactory.h | 14 +- src/contrib/cddadecoder/CddaIndexerSource.cpp | 170 ++++++++ src/contrib/cddadecoder/CddaIndexerSource.h | 71 ++++ src/contrib/cddadecoder/cddadecoder.vcxproj | 4 + .../cddadecoder/cddadecoder.vcxproj.filters | 12 + .../cddadecoder/cddadecoder_plugin.cpp | 13 +- src/contrib/cddadecoder/stdafx.h | 8 + .../win32globalhotkeys.vcxproj | 4 +- .../win32globalhotkeys_plugin.cpp | 4 +- src/core/audio/CrossfadeTransport.cpp | 7 +- src/core/audio/IStream.h | 1 + src/core/audio/PlaybackService.cpp | 41 +- src/core/audio/PlaybackService.h | 8 + src/core/audio/Player.cpp | 7 + src/core/audio/Player.h | 3 + src/core/audio/Stream.cpp | 11 +- src/core/audio/Stream.h | 2 + src/core/io/LocalFileStream.h | 1 + src/core/library/Indexer.cpp | 1 - src/core/library/track/TrackList.cpp | 9 +- src/core/sdk/IDataStream.h | 4 + src/core/sdk/IPlaybackService.h | 6 +- src/core/sdk/constants.h | 9 + src/core/support/PreferenceKeys.cpp | 1 + src/core/support/PreferenceKeys.h | 1 + src/glue/util/Playback.cpp | 8 +- src/glue/util/Playback.h | 5 +- src/musikbox/Main.cpp | 2 +- src/musikbox/app/layout/SettingsLayout.cpp | 37 +- src/musikbox/app/layout/SettingsLayout.h | 6 +- src/musikbox/app/util/GlobalHotkeys.cpp | 4 +- src/musikbox/app/window/TrackListView.cpp | 15 +- src/musikbox/app/window/TransportWindow.cpp | 10 +- src/musikbox/cursespp/Colors.cpp | 5 + src/musikbox/cursespp/Colors.h | 43 +- src/musikbox/data/locales/en_US.json | 1 + 40 files changed, 1085 insertions(+), 111 deletions(-) create mode 100644 src/contrib/cddadecoder/CddaDataModel.cpp create mode 100644 src/contrib/cddadecoder/CddaDataModel.h create mode 100644 src/contrib/cddadecoder/CddaIndexerSource.cpp create mode 100644 src/contrib/cddadecoder/CddaIndexerSource.h diff --git a/src/contrib/cddadecoder/CddaDataModel.cpp b/src/contrib/cddadecoder/CddaDataModel.cpp new file mode 100644 index 000000000..8accaa860 --- /dev/null +++ b/src/contrib/cddadecoder/CddaDataModel.cpp @@ -0,0 +1,366 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////////// + +#include "stdafx.h" +#include "CddaDataModel.h" +#include +#include +#include +#include + +#define CLASS_NAME L"CddaDataModelEventClass" +#define WINDOW_NAME L"CddaDataModelWindow" + +using AudioDiscPtr = CddaDataModel::AudioDiscPtr; +using DiscTrackPtr = CddaDataModel::DiscTrackPtr; + +static std::map WINDOW_TO_MODEL; + +static char FirstDriveFromMask(ULONG unitmask) { + char i; + for (i = 0; i < 26; ++i) { + if (unitmask & 0x1) { + break; + } + unitmask = unitmask >> 1; + } + return(i + 'A'); +} + +LRESULT CALLBACK CddaDataModel::StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_DEVICECHANGE: { + if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) { + DEV_BROADCAST_HDR* info = reinterpret_cast(lParam); + + if (info->dbch_devicetype == DBT_DEVTYP_VOLUME) { + DEV_BROADCAST_VOLUME* volumeInfo = reinterpret_cast(lParam); + + char driveLetter = FirstDriveFromMask(volumeInfo->dbcv_unitmask); + std::string drivePath = std::string(1, driveLetter) + ":"; + + if (GetDriveTypeA(drivePath.c_str()) == DRIVE_CDROM) { + auto it = WINDOW_TO_MODEL.find(hWnd); + + if (it != WINDOW_TO_MODEL.end()) { + it->second->OnAudioDiscInsertedOrRemoved(); + return 1; + } + } + } + } + + break; + } + + case WM_CLOSE: { + DestroyWindow(hWnd); + PostQuitMessage(0); + return 0; + } + + case WM_DESTROY: { + PostQuitMessage(0); + break; + } + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +CddaDataModel::CddaDataModel() { + +} + +CddaDataModel::~CddaDataModel() { + this->StopWindowThread(); +} + +void CddaDataModel::WindowThreadProc() { + HINSTANCE instance = (HINSTANCE) GetModuleHandle(nullptr); + + WNDCLASSW wndClass; + wndClass.style = CS_DBLCLKS; + wndClass.lpfnWndProc = CddaDataModel::StaticWndProc; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = 0; + wndClass.hInstance = instance; + wndClass.hIcon = NULL; + wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); + wndClass.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); + wndClass.lpszMenuName = NULL; + wndClass.lpszClassName = CLASS_NAME; + + if (!RegisterClassW(&wndClass)) { + DWORD dwError = GetLastError(); + if (dwError != ERROR_CLASS_ALREADY_EXISTS) { + return; + } + } + + RECT rc; + SetRect(&rc, -3200, -3200, 32, 32); + AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, false); + + HWND hwnd = CreateWindowW( + CLASS_NAME, + WINDOW_NAME, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + (rc.right - rc.left), + (rc.bottom - rc.top), + 0, + NULL, + instance, + 0); + + if (hwnd) { + this->messageWindow = hwnd; + WINDOW_TO_MODEL[hwnd] = this; + + ShowWindow(hwnd, SW_HIDE); + + MSG msg; + msg.message = WM_NULL; + + while (::GetMessage(&msg, NULL, 0, 0) > 0) { + if (msg.message == WM_QUIT) { + ::PostQuitMessage(0); + break; + } + + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + WINDOW_TO_MODEL.erase(WINDOW_TO_MODEL.find(hwnd)); + } + + DestroyWindow(hwnd); + UnregisterClass(CLASS_NAME, NULL); + this->messageWindow = nullptr; +} + +void CddaDataModel::StartWindowThread() { + Lock lock(this->windowMutex); + + if (!this->windowThread) { + this->windowThread.reset(new std::thread( + std::bind(&CddaDataModel::WindowThreadProc, this))); + } +} + +void CddaDataModel::StopWindowThread() { + Lock lock(this->windowMutex); + + if (this->windowThread) { + PostMessage(this->messageWindow, WM_QUIT, 0, 0); + this->windowThread->join(); + this->windowThread.reset(); + } +} + +std::vector CddaDataModel::GetAudioDiscs() { + std::vector result; + + for (int i = 0; i < 26; i++) { + char driveLetter = (char)('A' + i); + std::string drive = std::string(1, driveLetter) + ":"; + + AudioDiscPtr audioDisc = GetAudioDisc(driveLetter); + if (audioDisc && audioDisc->GetTrackCount() > 0) { + result.push_back(audioDisc); + } + } + + return result; +} + +AudioDiscPtr CddaDataModel::GetAudioDisc(char driveLetter) { + std::string drive = std::string(1, driveLetter) + ":"; + + if (GetDriveTypeA(drive.c_str()) == DRIVE_CDROM) { + drive = "\\\\.\\" + std::string(1, driveLetter) + ":"; + + HANDLE driveHandle = CreateFileA( + drive.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_FLAG_NO_BUFFERING | FILE_FLAG_RANDOM_ACCESS, + (HANDLE) nullptr); + + if (driveHandle == INVALID_HANDLE_VALUE) { + return AudioDiscPtr(); + } + + CDROM_TOC toc = { 0 }; + DWORD byteCount = 0; + + BOOL readable = DeviceIoControl( + driveHandle, + IOCTL_CDROM_READ_TOC, + NULL, + 0, + &toc, + sizeof(toc), + &byteCount, + 0); + + CloseHandle(driveHandle); + + if (readable) { + auto audioDisc = std::shared_ptr(new AudioDisc(driveLetter)); + + int trackCount = (toc.LastTrack - toc.FirstTrack) + 1; + + for (int j = 0; j < trackCount; j++) { + if (toc.TrackData[j].Control & 4) { + continue; /* data track */ + } + + auto start = MSF2UINT(toc.TrackData[j].Address) - FRAMES_PER_PREGAP; + auto end = MSF2UINT(toc.TrackData[j + 1].Address) - FRAMES_PER_PREGAP; + auto length = (end - start) * BYTES_PER_SECTOR; + double duration = (double)(length / 2 / sizeof(short)) / 44100.0f; + + audioDisc->AddTrack(std::make_shared( + toc.TrackData[j], driveLetter, j, duration)); + } + + if (audioDisc->GetTrackCount()) { + audioDisc->SetLeadout(std::make_shared( + toc.TrackData[trackCount], driveLetter, trackCount + 1, 0)); + + return audioDisc; + } + } + } + + return AudioDiscPtr(); +} + +void CddaDataModel::AddEventListener(EventListener* listener) { + Lock lock(eventListMutex); + this->listeners.insert(listener); +} + +void CddaDataModel::RemoveEventListener(EventListener* listener) { + Lock lock(eventListMutex); + auto it = this->listeners.find(listener); + if (it != this->listeners.end()) { + this->listeners.erase(it); + } +} + +void CddaDataModel::OnAudioDiscInsertedOrRemoved() { + Lock lock(eventListMutex); + for (auto l : this->listeners) { + l->OnAudioDiscInsertedOrRemoved(); + } +} + +/* * * * CddaDataModel::DiscTrack * * * */ + +CddaDataModel::DiscTrack::DiscTrack(TRACK_DATA& data, const char driveLetter, int number, double duration) { + this->driveLetter = driveLetter; + this->number = number; + this->duration = duration; + this->minutes = data.Address[1]; + this->seconds = data.Address[2]; + this->frames = data.Address[3]; +} + +/* http://www.robots.ox.ac.uk/~spline/cddb-howto.txt, appendix a */ +int CddaDataModel::DiscTrack::GetCddbSum() { + int n = (this->minutes * 60) + this->seconds; + int ret = 0; + + while (n > 0) { + ret += (n % 10); + n /= 10; + } + + return ret; +} + +/* * * * CddaDataModel::AudioDisc * * * */ + +CddaDataModel::AudioDisc::AudioDisc(char driveLetter) { + this->driveLetter = driveLetter; +} + +std::string CddaDataModel::AudioDisc::GetCddbId() { + int n = 0; + + for (int i = 0; i < this->GetTrackCount(); i++) { + n += this->GetTrackAt(i)->GetCddbSum(); + } + + auto first = this->GetTrackAt(0); + auto last = this->leadout; + + int t = ((last->GetMinutes() * 60) + last->GetSeconds()) - + ((first->GetMinutes() * 60) + first->GetSeconds()); + + int ret = ((n % 0xff) << 24 | t << 8 | this->GetTrackCount()); + + char buffer[9]; + snprintf(buffer, sizeof(buffer), "%08x", ret); + return std::string(buffer); +} + +void CddaDataModel::AudioDisc::SetLeadout(DiscTrackPtr leadout) { + this->leadout = leadout; +} + +void CddaDataModel::AudioDisc::AddTrack(DiscTrackPtr track) { + this->tracks.push_back(track); +} + +int CddaDataModel::AudioDisc::GetTrackCount() { + return (int) this->tracks.size(); +} + +DiscTrackPtr CddaDataModel::AudioDisc::GetTrackAt(int index) { + return this->tracks.at(index); +} + +std::string CddaDataModel::DiscTrack::GetFilePath() { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%c:\\%02d.cda", this->driveLetter, this->number + 1); + return std::string(buffer); +} \ No newline at end of file diff --git a/src/contrib/cddadecoder/CddaDataModel.h b/src/contrib/cddadecoder/CddaDataModel.h new file mode 100644 index 000000000..58af12998 --- /dev/null +++ b/src/contrib/cddadecoder/CddaDataModel.h @@ -0,0 +1,139 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include "ntddcdrm.h" +#include "devioctl.h" + +class CddaDataModel { + public: + class EventListener { + public: + virtual void OnAudioDiscInsertedOrRemoved() = 0; + }; + + struct DiscTrack { + public: + DiscTrack(TRACK_DATA& data, char driveLetter, int number, double duration); + + int GetCddbSum(); + + int GetNumber() { return this->number; } + int GetMinutes() { return this->minutes; } + int GetSeconds() { return this->seconds; } + int GetFrames() { return this->frames; } + double GetDuration() { return duration; } + + std::string GetFilePath(); + + private: + double duration; + char driveLetter; + int number; + int minutes; + int seconds; + int frames; + }; + + using DiscTrackPtr = std::shared_ptr; + + class AudioDisc { + public: + AudioDisc(char driveLetter); + + std::string GetCddbId(); + + void SetLeadout(DiscTrackPtr leadout); + void AddTrack(DiscTrackPtr track); + int GetTrackCount(); + DiscTrackPtr GetTrackAt(int index); + char GetDriveLetter() { return this->driveLetter; } + + private: + std::vector tracks; + DiscTrackPtr leadout; + char driveLetter; + }; + + using AudioDiscPtr = std::shared_ptr; + + static CddaDataModel& Instance() { + return Instance(true); + } + + static void Shutdown() { + Instance(false).StopWindowThread(); + } + + CddaDataModel& operator=(const CddaDataModel&) = delete; + CddaDataModel(const CddaDataModel&) = delete; + + std::vector GetAudioDiscs(); + AudioDiscPtr GetAudioDisc(char driveLetter); + + void AddEventListener(EventListener* listener); + void RemoveEventListener(EventListener* listener); + + private: + static CddaDataModel& Instance(bool start) { + static CddaDataModel model; + if (start) { + model.StartWindowThread(); + } + return model; + } + + using Thread = std::shared_ptr; + using Mutex = std::recursive_mutex; + using Lock = std::unique_lock; + + CddaDataModel(); + ~CddaDataModel(); + + void StartWindowThread(); + void StopWindowThread(); + void WindowThreadProc(); + void OnAudioDiscInsertedOrRemoved(); + + static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + Mutex windowMutex, eventListMutex; + Thread windowThread; + HWND messageWindow; + std::set listeners; +}; \ No newline at end of file diff --git a/src/contrib/cddadecoder/CddaDataStream.cpp b/src/contrib/cddadecoder/CddaDataStream.cpp index c6f62bb3d..3419c8eef 100755 --- a/src/contrib/cddadecoder/CddaDataStream.cpp +++ b/src/contrib/cddadecoder/CddaDataStream.cpp @@ -34,19 +34,23 @@ #include "stdafx.h" #include "CddaDataStream.h" +#include #include #include #include -#define FRAMES_PER_SECOND 75 -#define FRAMES_PER_MINUTE (60 * FRAMES_PER_SECOND) -#define FRAMES_PER_PREGAP (2 * FRAMES_PER_SECOND) +#if ENABLE_LOOKAHEAD_BUFFER + /* as it turns out having a small lookahead buffer is better than a + large one. if the buffer is too large, the device seems to start to + power down, then takes a while to power back up. we just want a little + but of wiggle room, but to generally keep the device reading small + chunks of data constantly. */ + #define MAX_SECTORS_IN_LOOKAHEAD 20 + #define MAX_SECTORS_PER_READ 10 + #define BUFFER_SIZE_BYTES (MAX_SECTORS_IN_LOOKAHEAD * BYTES_PER_SECTOR) +#endif -#define RAW_SECTOR_SIZE 2352 -#define MSF2UINT(hgs) ((hgs[1] * FRAMES_PER_MINUTE) + (hgs[2] * FRAMES_PER_SECOND) + (hgs[3])) - -static CddaDataStream* active = NULL; -static std::mutex activeMutex; +static std::mutex driveAccessMutex; /* one track can read at a time */ CddaDataStream::CddaDataStream() { this->closed = false; @@ -54,6 +58,11 @@ CddaDataStream::CddaDataStream() { this->position = this->length = 0; memset(&this->toc, 0, sizeof(this->toc)); this->startSector = this->stopSector = 0; + +#if ENABLE_LOOKAHEAD_BUFFER + this->lookahead = new char[BUFFER_SIZE_BYTES]; + this->lookaheadOffset = this->lookaheadTotal = 0; +#endif } CddaDataStream::~CddaDataStream() { @@ -93,7 +102,7 @@ bool CddaDataStream::Open(const char *uri, unsigned int options) { FILE_SHARE_READ, nullptr, OPEN_EXISTING, - 0, + FILE_FLAG_NO_BUFFERING | FILE_FLAG_RANDOM_ACCESS, (HANDLE) nullptr); if (this->drive == INVALID_HANDLE_VALUE) { @@ -124,23 +133,16 @@ bool CddaDataStream::Open(const char *uri, unsigned int options) { } /* MMC-3 Draft Revision 10g: Table 222 – Q Sub-channel control field */ - this->toc.TrackData[trackIndex - 1].Control &= 5; - if (!( - this->toc.TrackData[trackIndex - 1].Control == 0 || - this->toc.TrackData[trackIndex - 1].Control == 1)) - { + if (this->toc.TrackData[trackIndex - 1].Control & 4) { + /* nope, got a data track. */ this->Close(); return(false); } this->channels = 2; - if (this->toc.TrackData[trackIndex - 1].Control & 8) { - this->channels = 4; - } - this->startSector = MSF2UINT(this->toc.TrackData[trackIndex - 1].Address) - FRAMES_PER_PREGAP; this->stopSector = MSF2UINT(this->toc.TrackData[trackIndex].Address) - FRAMES_PER_PREGAP; - this->length = (this->stopSector - this->startSector) * RAW_SECTOR_SIZE; + this->length = (this->stopSector - this->startSector) * BYTES_PER_SECTOR; this->uri = uri; return true; @@ -166,6 +168,10 @@ void CddaDataStream::Destroy() { } PositionType CddaDataStream::Read(void* buffer, PositionType readBytes) { + if (this->position >= this->length) { + return 0; + } + DWORD actualBytesRead = 0; this->Read((BYTE*) buffer, readBytes, true, &actualBytesRead); return (PositionType) actualBytesRead; @@ -177,11 +183,16 @@ bool CddaDataStream::SetPosition(PositionType position) { } this->position = position; - while (this->position % (2 * this->channels)) { this->position++; } +#if ENABLE_LOOKAHEAD_BUFFER + this->RefillInternalBuffer(); + this->lookaheadOffset = 0; + Sleep(250); +#endif + return true; } @@ -209,24 +220,79 @@ const char* CddaDataStream::Uri() { return uri.c_str(); } +#if ENABLE_LOOKAHEAD_BUFFER +void CddaDataStream::RefillInternalBuffer() { + std::unique_lock lock(driveAccessMutex); + + LONGLONG pos = this->position; + int iterations = MAX_SECTORS_IN_LOOKAHEAD / MAX_SECTORS_PER_READ; + DWORD totalBytesRead = 0; + + RAW_READ_INFO rawReadInfo = { 0 }; + rawReadInfo.SectorCount = MAX_SECTORS_PER_READ; + rawReadInfo.TrackMode = CDDA; + + DWORD bytesActuallyRead = 0; + + while (iterations > 0) { + UINT sectorOffset = this->startSector + (int)(pos / BYTES_PER_SECTOR); + rawReadInfo.DiskOffset.QuadPart = sectorOffset * 2048; + + DeviceIoControl( + this->drive, + IOCTL_CDROM_RAW_READ, + &rawReadInfo, + sizeof(rawReadInfo), + &this->lookahead[totalBytesRead], + BYTES_PER_SECTOR * MAX_SECTORS_PER_READ, + &bytesActuallyRead, + 0); + + totalBytesRead += bytesActuallyRead; + pos += bytesActuallyRead; + + if (totalBytesRead == 0) { + break; + } + + --iterations; + } + + this->lookaheadOffset = 0; + this->lookaheadTotal = totalBytesRead; +} +#endif + HRESULT CddaDataStream::Read(PBYTE pbBuffer, DWORD dwBytesToRead, BOOL bAlign, LPDWORD pdwBytesRead) { if (this->closed) { pdwBytesRead = 0; return S_FALSE; } +#if ENABLE_LOOKAHEAD_BUFFER + size_t avail = this->lookaheadTotal - this->lookaheadOffset; + + if (avail == 0) { + this->RefillInternalBuffer(); + avail = this->lookaheadTotal; + } + + DWORD readSize = min(avail, (size_t) dwBytesToRead); + if (readSize >= 0) { + memcpy(pbBuffer, &this->lookahead[this->lookaheadOffset], readSize); + this->position += readSize; + this->lookaheadOffset += readSize; + } +#else + DWORD readSize = 0; LONGLONG pos = this->position; - size_t len = (size_t) dwBytesToRead; + UINT sectorOffset = this->startSector + (int)(pos / BYTES_PER_SECTOR); - UINT sectorOffset = this->startSector + (int)(pos / RAW_SECTOR_SIZE); - - RAW_READ_INFO rawReadInfo; - rawReadInfo.SectorCount = 1; + RAW_READ_INFO rawReadInfo = { 0 }; + rawReadInfo.SectorCount = dwBytesToRead / BYTES_PER_SECTOR; rawReadInfo.TrackMode = CDDA; rawReadInfo.DiskOffset.QuadPart = sectorOffset * 2048; - DWORD bytesActuallyRead = 0; - DeviceIoControl( this->drive, IOCTL_CDROM_RAW_READ, @@ -234,14 +300,15 @@ HRESULT CddaDataStream::Read(PBYTE pbBuffer, DWORD dwBytesToRead, BOOL bAlign, L sizeof(rawReadInfo), pbBuffer, dwBytesToRead, - &bytesActuallyRead, + &readSize, 0); - if (pdwBytesRead) { - *pdwBytesRead = bytesActuallyRead; - } + this->position += readSize; +#endif - this->position += bytesActuallyRead; + if (pdwBytesRead) { + *pdwBytesRead = readSize; + } return S_OK; } diff --git a/src/contrib/cddadecoder/CddaDataStream.h b/src/contrib/cddadecoder/CddaDataStream.h index af2eca0fc..6a7e3c733 100755 --- a/src/contrib/cddadecoder/CddaDataStream.h +++ b/src/contrib/cddadecoder/CddaDataStream.h @@ -38,6 +38,8 @@ #include "devioctl.h" #include +#define ENABLE_LOOKAHEAD_BUFFER 0 + using namespace musik::core::sdk; class CddaDataStream : public IDataStream { @@ -57,10 +59,13 @@ class CddaDataStream : public IDataStream { virtual bool Seekable(); virtual const char* Type(); virtual const char* Uri(); + virtual bool CanPrefetch() { return false; } int GetChannelCount(); private: + HRESULT Read(PBYTE pbBuffer, DWORD dwBytesToRead, BOOL bAlign, LPDWORD pdwBytesRead); + std::string uri; LONGLONG position, length; HANDLE drive; @@ -69,5 +74,10 @@ class CddaDataStream : public IDataStream { unsigned long channels; volatile bool closed; - HRESULT Read(PBYTE pbBuffer, DWORD dwBytesToRead, BOOL bAlign, LPDWORD pdwBytesRead); +#if ENABLE_LOOKAHEAD_BUFFER + char* lookahead; + DWORD lookaheadOffset; + DWORD lookaheadTotal; + void RefillInternalBuffer(); +#endif }; \ No newline at end of file diff --git a/src/contrib/cddadecoder/CddaDataStreamFactory.h b/src/contrib/cddadecoder/CddaDataStreamFactory.h index 2a5dea59f..756255c13 100755 --- a/src/contrib/cddadecoder/CddaDataStreamFactory.h +++ b/src/contrib/cddadecoder/CddaDataStreamFactory.h @@ -33,15 +33,17 @@ ////////////////////////////////////////////////////////////////////////////// #include +#include +#include using namespace musik::core::sdk; class CddaDataStreamFactory : public IDataStreamFactory { -public: - CddaDataStreamFactory(); - ~CddaDataStreamFactory(); + public: + CddaDataStreamFactory(); + ~CddaDataStreamFactory(); - virtual bool CanRead(const char *uri); - virtual IDataStream* Open(const char *uri, unsigned int options = 0); - virtual void Destroy(); + virtual bool CanRead(const char *uri); + virtual IDataStream* Open(const char *uri, unsigned int options = 0); + virtual void Destroy(); }; \ No newline at end of file diff --git a/src/contrib/cddadecoder/CddaIndexerSource.cpp b/src/contrib/cddadecoder/CddaIndexerSource.cpp new file mode 100644 index 000000000..efc942a52 --- /dev/null +++ b/src/contrib/cddadecoder/CddaIndexerSource.cpp @@ -0,0 +1,170 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////////// + +#include "stdafx.h" + +#include "CddaIndexerSource.h" + +#include + +#include +#include +#include + +using namespace musik::core::sdk; + +using DiscList = std::vector; +using DiscIdList = std::set; + +static std::mutex globalSinkMutex; +static musik::core::sdk::IIndexerNotifier* notifier; + +extern "C" __declspec(dllexport) void SetIndexerNotifier(musik::core::sdk::IIndexerNotifier* notifier) { + std::unique_lock lock(globalSinkMutex); + ::notifier = notifier; +} + +static std::vector tokenize(const std::string& str, char delim = '/') { + std::vector result; + std::istringstream iss(str); + std::string token; + + while (std::getline(iss, token, delim)) { + result.push_back(token); + } + + return result; +} + +static std::string createExternalId(const char driveLetter, const std::string& cddbId, int track) { + return "audiocd/" + std::string(1, driveLetter) + "/" + cddbId + "/" + std::to_string(track); +} + +static bool exists(DiscIdList& discs, const std::string& externalId) { + std::vector tokens = tokenize(externalId); + + if (tokens.size() < 4) { /* see format above */ + return false; + } + + return discs.find(tokens.at(2)) != discs.end(); +} + +static std::string labelForDrive(const char driveLetter) { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "[audio cd %c:\\]", tolower(driveLetter)); + return std::string(buffer); +} + +CddaIndexerSource::CddaIndexerSource() +: model(CddaDataModel::Instance()) { + model.AddEventListener(this); +} + +CddaIndexerSource::~CddaIndexerSource() { + model.RemoveEventListener(this); +} + +void CddaIndexerSource::Destroy() { + delete this; +} + +void CddaIndexerSource::RefreshModel() { + this->discs = model.GetAudioDiscs(); + discIds.clear(); + for (auto disc : discs) { + discIds.insert(disc->GetCddbId()); + } +} + +void CddaIndexerSource::OnAudioDiscInsertedOrRemoved() { + std::unique_lock lock(globalSinkMutex); + if (::notifier) { + ::notifier->ScheduleRescan(this); + } +} + +void CddaIndexerSource::OnBeforeScan() { + this->RefreshModel(); +} + +void CddaIndexerSource::OnAfterScan() { + /* nothing to do... */ +} + +void CddaIndexerSource::Scan(musik::core::sdk::IIndexerWriter* indexer) { + for (auto disc : this->discs) { + char driveLetter = disc->GetDriveLetter(); + std::string cddbId = disc->GetCddbId(); + + for (int i = 0; i < disc->GetTrackCount(); i++) { + auto discTrack = disc->GetTrackAt(i); + + std::string externalId = createExternalId(driveLetter, cddbId, i); + std::string label = labelForDrive(driveLetter); + std::string title = "track #" + std::to_string(i + 1); + + auto track = indexer->CreateWriter(); + + track->SetValue("album", label.c_str()); + track->SetValue("artist", label.c_str()); + track->SetValue("album_artist", label.c_str()); + track->SetValue("genre", label.c_str()); + track->SetValue("title", title.c_str()); + track->SetValue("filename", discTrack->GetFilePath().c_str()); + track->SetValue("duration", std::to_string((int) round(discTrack->GetDuration())).c_str()); + track->SetValue("track", std::to_string(i + 1).c_str()); + + indexer->Save(this, track, externalId.c_str()); + + track->Release(); + } + } + + discIds.clear(); +} + +void CddaIndexerSource::Scan( + IIndexerWriter* indexer, + IRetainedTrackWriter* track, + const char* externalId) +{ + if (!exists(this->discIds, externalId)) { + indexer->RemoveByExternalId(this, externalId); + } +} + +int CddaIndexerSource::SourceId() { + return std::hash()(PLUGIN_NAME); +} \ No newline at end of file diff --git a/src/contrib/cddadecoder/CddaIndexerSource.h b/src/contrib/cddadecoder/CddaIndexerSource.h new file mode 100644 index 000000000..cc1594a81 --- /dev/null +++ b/src/contrib/cddadecoder/CddaIndexerSource.h @@ -0,0 +1,71 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////////// + +#include +#include "CddaDataModel.h" + +#include +#include + +class CddaIndexerSource : + public musik::core::sdk::IIndexerSource, + public CddaDataModel::EventListener +{ + public: + CddaIndexerSource(); + ~CddaIndexerSource(); + + /* IIndexerSource */ + virtual void Destroy(); + virtual void OnBeforeScan(); + virtual void OnAfterScan(); + virtual int SourceId(); + + virtual void Scan(musik::core::sdk::IIndexerWriter* indexer); + + virtual void Scan( + musik::core::sdk::IIndexerWriter* indexer, + musik::core::sdk::IRetainedTrackWriter* track, + const char* externalId); + + /* CddaDataModel::EventListener */ + virtual void OnAudioDiscInsertedOrRemoved(); + + private: + void RefreshModel(); + + CddaDataModel& model; + std::set discIds; + std::vector discs; +}; \ No newline at end of file diff --git a/src/contrib/cddadecoder/cddadecoder.vcxproj b/src/contrib/cddadecoder/cddadecoder.vcxproj index 0140a17de..2a5556ee7 100755 --- a/src/contrib/cddadecoder/cddadecoder.vcxproj +++ b/src/contrib/cddadecoder/cddadecoder.vcxproj @@ -99,18 +99,22 @@ + + + + diff --git a/src/contrib/cddadecoder/cddadecoder.vcxproj.filters b/src/contrib/cddadecoder/cddadecoder.vcxproj.filters index 478d234a1..5aa9f9894 100755 --- a/src/contrib/cddadecoder/cddadecoder.vcxproj.filters +++ b/src/contrib/cddadecoder/cddadecoder.vcxproj.filters @@ -31,6 +31,12 @@ plugin + + plugin + + + plugin + @@ -57,5 +63,11 @@ plugin + + plugin + + + plugin + \ No newline at end of file diff --git a/src/contrib/cddadecoder/cddadecoder_plugin.cpp b/src/contrib/cddadecoder/cddadecoder_plugin.cpp index c92bb7a15..d107fa2e1 100644 --- a/src/contrib/cddadecoder/cddadecoder_plugin.cpp +++ b/src/contrib/cddadecoder/cddadecoder_plugin.cpp @@ -36,17 +36,22 @@ #include "CddaDecoderFactory.h" #include "CddaDataStreamFactory.h" +#include "CddaIndexerSource.h" #include #include -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { +BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved) { + if (reason == DLL_PROCESS_DETACH) { + CddaDataModel::Shutdown(); + } + return true; } class CddaDecoderPlugin : public musik::core::sdk::IPlugin { virtual void Destroy() { delete this; }; - virtual const char* Name() { return "CD Audio IDecoder, IDataStream"; } + virtual const char* Name() { return PLUGIN_NAME; } virtual const char* Version() { return "0.4.0"; } virtual const char* Author() { return "Björn Olievier, clangen"; } virtual int SdkVersion() { return musik::core::sdk::SdkVersion; } @@ -63,3 +68,7 @@ extern "C" __declspec(dllexport) IDecoderFactory* GetDecoderFactory() { extern "C" __declspec(dllexport) IDataStreamFactory* GetDataStreamFactory() { return new CddaDataStreamFactory(); } + +extern "C" __declspec(dllexport) IIndexerSource* GetIndexerSource() { + return new CddaIndexerSource(); +} \ No newline at end of file diff --git a/src/contrib/cddadecoder/stdafx.h b/src/contrib/cddadecoder/stdafx.h index 5a08a17fb..4c219834f 100644 --- a/src/contrib/cddadecoder/stdafx.h +++ b/src/contrib/cddadecoder/stdafx.h @@ -38,5 +38,13 @@ #define WINVER 0x0502 #define _WIN32_WINNT 0x0502 +#define PLUGIN_NAME "CD Audio IDecoder, IDataStream" + +#define FRAMES_PER_SECOND 75 +#define FRAMES_PER_MINUTE (60 * FRAMES_PER_SECOND) +#define FRAMES_PER_PREGAP (2 * FRAMES_PER_SECOND) +#define BYTES_PER_SECTOR 2352 +#define MSF2UINT(hgs) ((hgs[1] * FRAMES_PER_MINUTE) + (hgs[2] * FRAMES_PER_SECOND) + (hgs[3])) + #include #include \ No newline at end of file diff --git a/src/contrib/win32globalhotkeys/win32globalhotkeys.vcxproj b/src/contrib/win32globalhotkeys/win32globalhotkeys.vcxproj index 919f61cbc..04495e46b 100644 --- a/src/contrib/win32globalhotkeys/win32globalhotkeys.vcxproj +++ b/src/contrib/win32globalhotkeys/win32globalhotkeys.vcxproj @@ -42,7 +42,7 @@ <_ProjectFileVersion>14.0.25123.0 - $(SolutionDir)bin\$(Configuration)\plugins + $(SolutionDir)bin\$(Configuration)\plugins\ ./obj/$(Configuration)\ true MinimumRecommendedRules.ruleset @@ -50,7 +50,7 @@ - $(SolutionDir)bin\$(Configuration)\plugins + $(SolutionDir)bin\$(Configuration)\plugins\ ./obj/$(Configuration)\ MinimumRecommendedRules.ruleset diff --git a/src/contrib/win32globalhotkeys/win32globalhotkeys_plugin.cpp b/src/contrib/win32globalhotkeys/win32globalhotkeys_plugin.cpp index 9d457be1a..c3af0b49f 100644 --- a/src/contrib/win32globalhotkeys/win32globalhotkeys_plugin.cpp +++ b/src/contrib/win32globalhotkeys/win32globalhotkeys_plugin.cpp @@ -124,7 +124,9 @@ LRESULT CALLBACK ShellProc(int code, WPARAM wParam, LPARAM lParam) { } void installHook() { - if (!::hook) { + /* note: don't install the hook if we're debugging, otherwise inptuts + get SUPER laggy. */ + if (!IsDebuggerPresent() && !::hook) { hook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)ShellProc, module, 0L); } } diff --git a/src/core/audio/CrossfadeTransport.cpp b/src/core/audio/CrossfadeTransport.cpp index 6cc7f4d38..8119b0037 100644 --- a/src/core/audio/CrossfadeTransport.cpp +++ b/src/core/audio/CrossfadeTransport.cpp @@ -224,7 +224,7 @@ void CrossfadeTransport::SetVolume(double volume) { active.SetVolume(volume); next.SetVolume(volume); } - + if (oldVolume != this->volume) { this->SetMuted(false); this->VolumeChanged(); @@ -237,7 +237,10 @@ void CrossfadeTransport::OnPlayerPrepared(Player* player) { int durationMs = (int)(player->GetDuration() * 1000.0f); double mixpointOffset = 0.0f; - bool canFade = (durationMs > CROSSFADE_DURATION_MS * 4); + + bool canFade = + player->HasCapability(Capability::Prebuffer) && + (durationMs > CROSSFADE_DURATION_MS * 4); /* if the track isn't long enough don't set a mixpoint, and disable the crossfade functionality */ diff --git a/src/core/audio/IStream.h b/src/core/audio/IStream.h index 459c61fcd..2b2404881 100644 --- a/src/core/audio/IStream.h +++ b/src/core/audio/IStream.h @@ -58,6 +58,7 @@ namespace musik { namespace core { namespace audio { virtual double GetDuration() = 0; virtual bool OpenStream(std::string uri) = 0; virtual void Interrupt() = 0; + virtual int GetCapabilities() = 0; }; typedef std::shared_ptr IStreamPtr; diff --git a/src/core/audio/PlaybackService.cpp b/src/core/audio/PlaybackService.cpp index a9206f8c0..524c9d89a 100755 --- a/src/core/audio/PlaybackService.cpp +++ b/src/core/audio/PlaybackService.cpp @@ -72,6 +72,7 @@ using Editor = PlaybackService::Editor; #define MESSAGE_SHUFFLED 1006 #define MESSAGE_NOTIFY_EDITED 1007 #define MESSAGE_NOTIFY_RESET 1008 +#define MESSAGE_SEEK 1009 class StreamMessage : public Message { public: @@ -110,6 +111,9 @@ static inline void loadPreferences( int repeatMode = prefs->GetInt(keys::RepeatMode, RepeatNone); repeatMode = (repeatMode > RepeatList || repeatMode < RepeatNone) ? RepeatNone : repeatMode; playback.SetRepeatMode((RepeatMode) repeatMode); + + int timeChangeMode = prefs->GetInt(keys::TimeChangeMode, TimeChangeScrub); + playback.SetTimeChangeMode((TimeChangeMode) timeChangeMode); } static inline void savePreferences( @@ -118,6 +122,7 @@ static inline void savePreferences( { prefs->SetDouble(keys::Volume, playback.GetVolume()); prefs->SetInt(keys::RepeatMode, playback.GetRepeatMode()); + prefs->SetInt(keys::TimeChangeMode, playback.GetTimeChangeMode()); } PlaybackService::PlaybackService( @@ -135,7 +140,9 @@ PlaybackService::PlaybackService( transport.PlaybackEvent.connect(this, &PlaybackService::OnPlaybackEvent); transport.VolumeChanged.connect(this, &PlaybackService::OnVolumeChanged); transport.TimeChanged.connect(this, &PlaybackService::OnTimeChanged); + library->Indexer()->Finished.connect(this, &PlaybackService::OnIndexerFinished); loadPreferences(this->transport, *this, prefs); + this->seekPosition = -1.0f; this->index = NO_POSITION; this->nextIndex = NO_POSITION; this->InitRemotes(); @@ -214,6 +221,14 @@ void PlaybackService::SetRepeatMode(RepeatMode mode) { } } +musik::core::sdk::TimeChangeMode PlaybackService::GetTimeChangeMode() { + return this->timeChangeMode; +} + +void PlaybackService::SetTimeChangeMode(TimeChangeMode mode) { + this->timeChangeMode = mode; +} + void PlaybackService::ToggleShuffle() { std::unique_lock lock(this->playlistMutex); @@ -346,6 +361,12 @@ void PlaybackService::ProcessMessage(IMessage &message) { this->QueueEdited(); } + else if (type == MESSAGE_SEEK) { + if (this->seekPosition != -1.0f) { + this->transport.SetPosition(this->seekPosition + 0.5f); + this->seekPosition = -1.0f; + } + } } void PlaybackService::NotifyRemotesModeChanged() { @@ -578,11 +599,23 @@ void PlaybackService::SetVolume(double vol) { } double PlaybackService::GetPosition() { + if (this->timeChangeMode == TimeChangeSeek && this->seekPosition != -1.0f) { + return this->seekPosition; + } + return transport.Position(); } void PlaybackService::SetPosition(double seconds) { - transport.SetPosition(seconds); + if (this->timeChangeMode == TimeChangeSeek) { + seconds = std::max(seconds, (double) 0.0); + this->seekPosition = seconds; + this->TimeChanged(seconds); + messageQueue.Debounce(Message::Create(this, MESSAGE_SEEK), 500); + } + else { /* TimeChangeScrub */ + transport.SetPosition(seconds); + } } double PlaybackService::GetDuration() { @@ -698,6 +731,12 @@ void PlaybackService::OnTimeChanged(double time) { POST(this, MESSAGE_TIME_CHANGED, 0, 0); } +void PlaybackService::OnIndexerFinished(int trackCount) { + std::unique_lock lock(this->playlistMutex); + this->playlist.ClearCache(); + this->unshuffled.ClearCache(); +} + /* our Editor interface. we proxy all of the ITrackListEditor methods so we can track and maintain our currently playing index as tracks move around in the backing store. lots of annoying book keeping. we have to do it this diff --git a/src/core/audio/PlaybackService.h b/src/core/audio/PlaybackService.h index efec8e115..0fadc24df 100755 --- a/src/core/audio/PlaybackService.h +++ b/src/core/audio/PlaybackService.h @@ -103,6 +103,8 @@ namespace musik { namespace core { namespace audio { virtual void CopyFrom(const musik::core::sdk::ITrackList* source); virtual void Play(const musik::core::sdk::ITrackList* source, size_t index); virtual musik::core::sdk::ITrackListEditor* EditPlaylist(); + virtual musik::core::sdk::TimeChangeMode GetTimeChangeMode(); + virtual void SetTimeChangeMode(musik::core::sdk::TimeChangeMode); /* app-specific implementation. similar to some SDK methods, but use concrete data types with known optimizations */ @@ -167,6 +169,8 @@ namespace musik { namespace core { namespace audio { void OnTrackChanged(size_t pos, musik::core::TrackPtr track); void OnVolumeChanged(); void OnTimeChanged(double time); + void OnIndexerFinished(int trackCount); + void NotifyRemotesModeChanged(); void PrepareNextTrack(); void InitRemotes(); @@ -184,7 +188,11 @@ namespace musik { namespace core { namespace audio { musik::core::ILibraryPtr library; musik::core::audio::ITransport& transport; size_t index, nextIndex; + musik::core::sdk::RepeatMode repeatMode; + musik::core::sdk::TimeChangeMode timeChangeMode; + + double seekPosition; musik::core::runtime::IMessageQueue& messageQueue; }; diff --git a/src/core/audio/Player.cpp b/src/core/audio/Player.cpp index 7e38b4adf..acf36467d 100644 --- a/src/core/audio/Player.cpp +++ b/src/core/audio/Player.cpp @@ -251,6 +251,13 @@ int Player::State() { return this->state; } +bool Player::HasCapability(Capability c) { + if (this->stream) { + return (this->stream->GetCapabilities() & (int) c) != 0; + } + return false; +} + void Player::UpdateNextMixPointTime() { const double position = this->GetPositionInternal(); diff --git a/src/core/audio/Player.h b/src/core/audio/Player.h index 85dc48a0b..542dac567 100644 --- a/src/core/audio/Player.h +++ b/src/core/audio/Player.h @@ -36,6 +36,7 @@ #include #include +#include #include #include @@ -85,6 +86,8 @@ namespace musik { namespace core { namespace audio { void AddMixPoint(int id, double time); + bool HasCapability(musik::core::sdk::Capability capability); + std::string GetUrl() const { return this->url; } private: diff --git a/src/core/audio/Stream.cpp b/src/core/audio/Stream.cpp index 3119a73e0..8c034a97b 100644 --- a/src/core/audio/Stream.cpp +++ b/src/core/audio/Stream.cpp @@ -36,6 +36,7 @@ #include "Stream.h" #include "Streams.h" +#include #include using namespace musik::core::audio; @@ -62,6 +63,7 @@ Stream::Stream(int samplesPerChannel, double bufferLengthSeconds, unsigned int o , decoderChannels(0) , decoderSamplePosition(0) , done(false) +, capabilities(0) , remainder(nullptr) , rawBuffer(nullptr) { if ((this->options & NoDSP) == 0) { @@ -114,6 +116,10 @@ double Stream::GetDuration() { return this->decoder ? this->decoder->GetDuration() : -1.0f; } +int Stream::GetCapabilities() { + return this->capabilities; +} + bool Stream::OpenStream(std::string uri) { musik::debug::info(TAG, "opening " + uri); @@ -129,7 +135,10 @@ bool Stream::OpenStream(std::string uri) { this->decoder = streams::GetDecoderForDataStream(this->dataStream); if (this->decoder) { - this->RefillInternalBuffers(); + if (this->dataStream->CanPrefetch()) { + this->capabilities |= (int) musik::core::sdk::Capability::Prebuffer; + this->RefillInternalBuffers(); + } return true; } diff --git a/src/core/audio/Stream.h b/src/core/audio/Stream.h index a570bd7d8..2f37901e7 100644 --- a/src/core/audio/Stream.h +++ b/src/core/audio/Stream.h @@ -68,6 +68,7 @@ namespace musik { namespace core { namespace audio { virtual double GetDuration(); virtual bool OpenStream(std::string uri); virtual void Interrupt(); + virtual int GetCapabilities(); private: bool GetNextBufferFromDecoder(); @@ -96,6 +97,7 @@ namespace musik { namespace core { namespace audio { int bufferCount; bool done; double bufferLengthSeconds; + int capabilities; float* rawBuffer; diff --git a/src/core/io/LocalFileStream.h b/src/core/io/LocalFileStream.h index e52d4176a..34a960d7f 100644 --- a/src/core/io/LocalFileStream.h +++ b/src/core/io/LocalFileStream.h @@ -59,6 +59,7 @@ namespace musik { namespace core { namespace io { virtual bool Seekable(); virtual const char* Type(); virtual const char* Uri(); + virtual bool CanPrefetch() { return true; } private: std::string extension; diff --git a/src/core/library/Indexer.cpp b/src/core/library/Indexer.cpp index 75d12649b..16dd921f8 100644 --- a/src/core/library/Indexer.cpp +++ b/src/core/library/Indexer.cpp @@ -832,4 +832,3 @@ void Indexer::ScheduleRescan(IIndexerSource* source) { this->Schedule(SyncType::Sources, source); } } - diff --git a/src/core/library/track/TrackList.cpp b/src/core/library/track/TrackList.cpp index c891a20d8..fa5a61831 100755 --- a/src/core/library/track/TrackList.cpp +++ b/src/core/library/track/TrackList.cpp @@ -123,13 +123,16 @@ TrackPtr TrackList::Get(size_t index) const { new TrackMetadataQuery(target, this->library)); this->library->Enqueue(query, ILibrary::QuerySynchronous); - this->AddToCache(id, query->Result()); - return query->Result(); + if (query->GetStatus() == IQuery::Finished) { + this->AddToCache(id, query->Result()); + return query->Result(); + } } catch (...) { - return TrackPtr(); } + + return TrackPtr(); } IRetainedTrack* TrackList::GetRetainedTrack(size_t index) const { diff --git a/src/core/sdk/IDataStream.h b/src/core/sdk/IDataStream.h index b16968c33..195df515b 100644 --- a/src/core/sdk/IDataStream.h +++ b/src/core/sdk/IDataStream.h @@ -40,6 +40,7 @@ namespace musik { namespace core { namespace sdk { class IDataStream { public: + /* v1 */ virtual bool Open(const char *uri, unsigned int options = 0) = 0; virtual bool Close() = 0; virtual void Interrupt() = 0; @@ -52,6 +53,9 @@ namespace musik { namespace core { namespace sdk { virtual long Length() = 0; virtual const char* Type() = 0; virtual const char* Uri() = 0; + + /* v5 */ + virtual bool CanPrefetch() = 0; }; } } } \ No newline at end of file diff --git a/src/core/sdk/IPlaybackService.h b/src/core/sdk/IPlaybackService.h index ab7805fab..58bda3305 100644 --- a/src/core/sdk/IPlaybackService.h +++ b/src/core/sdk/IPlaybackService.h @@ -77,10 +77,14 @@ namespace musik { namespace core { namespace sdk { /* sdk v2 */ virtual IRetainedTrack* GetPlayingTrack() = 0; - /* sdk v3*/ + /* sdk v3 */ virtual void CopyFrom(const ITrackList* trackList) = 0; virtual void Play(const ITrackList* source, size_t index) = 0; virtual ITrackListEditor* EditPlaylist() = 0; + + /* sdk v5 */ + virtual musik::core::sdk::TimeChangeMode GetTimeChangeMode() = 0; + virtual void SetTimeChangeMode(musik::core::sdk::TimeChangeMode) = 0; }; } } } diff --git a/src/core/sdk/constants.h b/src/core/sdk/constants.h index 2348aaff5..4cffe0cd1 100644 --- a/src/core/sdk/constants.h +++ b/src/core/sdk/constants.h @@ -78,6 +78,15 @@ namespace musik { ChannelSideRight = 256 }; + enum TimeChangeMode { + TimeChangeSeek, + TimeChangeScrub + }; + + enum class Capability : int { + Prebuffer = 0x01 + }; + namespace category { static const char* Album = "album"; static const char* Artist = "artist"; diff --git a/src/core/support/PreferenceKeys.cpp b/src/core/support/PreferenceKeys.cpp index ce0121c23..d93c0f229 100644 --- a/src/core/support/PreferenceKeys.cpp +++ b/src/core/support/PreferenceKeys.cpp @@ -48,6 +48,7 @@ namespace musik { namespace core { namespace prefs { const std::string keys::SyncOnStartup = "SyncOnStartup"; const std::string keys::Volume = "Volume"; const std::string keys::RepeatMode = "RepeatMode"; + const std::string keys::TimeChangeMode = "TimeChangeMode"; const std::string keys::OutputPlugin = "OutputPlugin"; const std::string keys::Transport = "Transport"; const std::string keys::Locale = "Locale"; diff --git a/src/core/support/PreferenceKeys.h b/src/core/support/PreferenceKeys.h index 8fbe353f5..27f19b7e8 100644 --- a/src/core/support/PreferenceKeys.h +++ b/src/core/support/PreferenceKeys.h @@ -52,6 +52,7 @@ namespace musik { namespace core { namespace prefs { extern const std::string SyncOnStartup; extern const std::string Volume; extern const std::string RepeatMode; + extern const std::string TimeChangeMode; extern const std::string OutputPlugin; extern const std::string Transport; extern const std::string Locale; diff --git a/src/glue/util/Playback.cpp b/src/glue/util/Playback.cpp index 46c8d5d3c..2cc12ca9c 100644 --- a/src/glue/util/Playback.cpp +++ b/src/glue/util/Playback.cpp @@ -63,12 +63,12 @@ namespace musik { transport.SetVolume(transport.Volume() - delta); } - void SeekForward(ITransport& transport) { - transport.SetPosition(transport.Position() + 10.0f); + void SeekForward(IPlaybackService& playback) { + playback.SetPosition(playback.GetPosition() + 10.0f); } - void SeekBack(ITransport& transport) { - transport.SetPosition(transport.Position() - 10.0f); + void SeekBack(IPlaybackService& playback) { + playback.SetPosition(playback.GetPosition() - 10.0f); } } } diff --git a/src/glue/util/Playback.h b/src/glue/util/Playback.h index 77e11e554..e250b6060 100644 --- a/src/glue/util/Playback.h +++ b/src/glue/util/Playback.h @@ -35,14 +35,15 @@ #pragma once #include +#include namespace musik { namespace glue { namespace playback { void VolumeUp(musik::core::audio::ITransport& transport); void VolumeDown(musik::core::audio::ITransport& transport); - void SeekForward(musik::core::audio::ITransport& transport); - void SeekBack(musik::core::audio::ITransport& transport); + void SeekForward(musik::core::sdk::IPlaybackService& playback); + void SeekBack(musik::core::sdk::IPlaybackService& playback); void PauseOrResume(musik::core::audio::ITransport& transport); } } diff --git a/src/musikbox/Main.cpp b/src/musikbox/Main.cpp index c4fe257d8..08194889e 100644 --- a/src/musikbox/Main.cpp +++ b/src/musikbox/Main.cpp @@ -174,7 +174,7 @@ int main(int argc, char* argv[]) Layout libraryLayout(new LibraryLayout(playback, library)); Layout consoleLayout(new ConsoleLayout(transport, library)); - Layout settingsLayout(new SettingsLayout(library, transport)); + Layout settingsLayout(new SettingsLayout(library, playback, transport)); Main mainLayout(new MainLayout(library)); diff --git a/src/musikbox/app/layout/SettingsLayout.cpp b/src/musikbox/app/layout/SettingsLayout.cpp index ea1242be9..8f3d52946 100755 --- a/src/musikbox/app/layout/SettingsLayout.cpp +++ b/src/musikbox/app/layout/SettingsLayout.cpp @@ -92,13 +92,15 @@ static bool showDotfiles = false; SettingsLayout::SettingsLayout( musik::core::ILibraryPtr library, + musik::core::sdk::IPlaybackService& playback, musik::glue::audio::MasterTransport& transport) : LayoutBase() , library(library) , indexer(library->Indexer()) , transport(transport) +, playback(playback) , pathsUpdated(false) { - this->libraryPrefs = Preferences::ForComponent(core::prefs::components::Settings); + this->prefs = Preferences::ForComponent(core::prefs::components::Settings); this->browseAdapter.reset(new DirectoryAdapter()); this->addedPathsAdapter.reset(new SimpleScrollAdapter()); this->InitializeWindows(); @@ -109,18 +111,24 @@ SettingsLayout::~SettingsLayout() { void SettingsLayout::OnCheckboxChanged(cursespp::Checkbox* cb, bool checked) { if (cb == syncOnStartupCheckbox.get()) { - this->libraryPrefs->SetBool(core::prefs::keys::SyncOnStartup, checked); - this->libraryPrefs->Save(); + this->prefs->SetBool(core::prefs::keys::SyncOnStartup, checked); + this->prefs->Save(); } else if (cb == removeCheckbox.get()) { - this->libraryPrefs->SetBool(core::prefs::keys::RemoveMissingFiles, checked); - this->libraryPrefs->Save(); + this->prefs->SetBool(core::prefs::keys::RemoveMissingFiles, checked); + this->prefs->Save(); } else if (cb == dotfileCheckbox.get()) { showDotfiles = !showDotfiles; this->browseAdapter->SetDotfilesVisible(showDotfiles); this->browseList->OnAdapterChanged(); } + else if (cb == seekScrubCheckbox.get()) { + TimeChangeMode mode = cb->IsChecked() ? TimeChangeSeek : TimeChangeScrub; + this->prefs->SetInt(core::prefs::keys::TimeChangeMode, (int) mode); + this->seekScrubCheckbox->SetChecked(this->prefs->GetInt(core::prefs::keys::TimeChangeMode) == (int)TimeChangeSeek); + this->playback.SetTimeChangeMode(mode); + } #ifdef ENABLE_256_COLOR_OPTION else if (cb == paletteCheckbox.get()) { ColorThemeOverlay::Show256ColorsInfo( @@ -222,6 +230,7 @@ void SettingsLayout::OnLayout() { this->dotfileCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT); this->syncOnStartupCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT); this->removeCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT); + this->seekScrubCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT); } void SettingsLayout::RefreshAddedPaths() { @@ -307,6 +316,7 @@ void SettingsLayout::InitializeWindows() { CREATE_CHECKBOX(this->dotfileCheckbox, _TSTR("settings_show_dotfiles")); CREATE_CHECKBOX(this->syncOnStartupCheckbox, _TSTR("settings_sync_on_startup")); CREATE_CHECKBOX(this->removeCheckbox, _TSTR("settings_remove_missing")); + CREATE_CHECKBOX(this->seekScrubCheckbox, _TSTR("settings_seek_not_scrub")); int order = 0; this->browseList->SetFocusOrder(order++); @@ -323,6 +333,7 @@ void SettingsLayout::InitializeWindows() { this->dotfileCheckbox->SetFocusOrder(order++); this->syncOnStartupCheckbox->SetFocusOrder(order++); this->removeCheckbox->SetFocusOrder(order++); + this->seekScrubCheckbox->SetFocusOrder(order++); this->AddWindow(this->browseLabel); this->AddWindow(this->addedPathsLabel); @@ -340,6 +351,7 @@ void SettingsLayout::InitializeWindows() { this->AddWindow(this->dotfileCheckbox); this->AddWindow(this->syncOnStartupCheckbox); this->AddWindow(this->removeCheckbox); + this->AddWindow(this->seekScrubCheckbox); } void SettingsLayout::SetShortcutsWindow(ShortcutsWindow* shortcuts) { @@ -370,7 +382,7 @@ void SettingsLayout::OnVisibilityChanged(bool visible) { } void SettingsLayout::CheckShowFirstRunDialog() { - if (!this->libraryPrefs->GetBool(box::prefs::keys::FirstRunSettingsDisplayed)) { + if (!this->prefs->GetBool(box::prefs::keys::FirstRunSettingsDisplayed)) { if (!this->firstRunDialog) { this->firstRunDialog.reset(new DialogOverlay()); @@ -391,7 +403,7 @@ void SettingsLayout::CheckShowFirstRunDialog() { "ENTER", _TSTR("button_ok"), [this](std::string key) { - this->libraryPrefs->SetBool(box::prefs::keys::FirstRunSettingsDisplayed, true); + this->prefs->SetBool(box::prefs::keys::FirstRunSettingsDisplayed, true); this->firstRunDialog.reset(); }); @@ -401,15 +413,16 @@ void SettingsLayout::CheckShowFirstRunDialog() { } void SettingsLayout::LoadPreferences() { - this->syncOnStartupCheckbox->SetChecked(this->libraryPrefs->GetBool(core::prefs::keys::SyncOnStartup, true)); - this->removeCheckbox->SetChecked(this->libraryPrefs->GetBool(core::prefs::keys::RemoveMissingFiles, true)); + this->syncOnStartupCheckbox->SetChecked(this->prefs->GetBool(core::prefs::keys::SyncOnStartup, true)); + this->removeCheckbox->SetChecked(this->prefs->GetBool(core::prefs::keys::RemoveMissingFiles, true)); + this->seekScrubCheckbox->SetChecked(this->prefs->GetInt(core::prefs::keys::TimeChangeMode, TimeChangeScrub) == TimeChangeSeek); /* locale */ this->localeDropdown->SetText(arrow + _TSTR("settings_selected_locale") + i18n::Locale::Instance().GetSelectedLocale()); /* color theme */ - bool disableCustomColors = this->libraryPrefs->GetBool(box::prefs::keys::DisableCustomColors); - std::string colorTheme = this->libraryPrefs->GetString(box::prefs::keys::ColorTheme); + bool disableCustomColors = this->prefs->GetBool(box::prefs::keys::DisableCustomColors); + std::string colorTheme = this->prefs->GetString(box::prefs::keys::ColorTheme); if (colorTheme == "" && !disableCustomColors) { colorTheme = _TSTR("settings_default_theme_name"); @@ -423,7 +436,7 @@ void SettingsLayout::LoadPreferences() { #ifdef ENABLE_256_COLOR_OPTION this->paletteCheckbox->CheckChanged.disconnect(this); this->paletteCheckbox->SetChecked( - this->libraryPrefs->GetBool(box::prefs::keys::UsePaletteColors, true)); + this->prefs->GetBool(box::prefs::keys::UsePaletteColors, true)); this->paletteCheckbox->CheckChanged.connect(this, &SettingsLayout::OnCheckboxChanged); #endif diff --git a/src/musikbox/app/layout/SettingsLayout.h b/src/musikbox/app/layout/SettingsLayout.h index 35157263e..c5ef06bdf 100755 --- a/src/musikbox/app/layout/SettingsLayout.h +++ b/src/musikbox/app/layout/SettingsLayout.h @@ -69,6 +69,7 @@ namespace musik { public: SettingsLayout( musik::core::ILibraryPtr library, + musik::core::sdk::IPlaybackService& playback, musik::glue::audio::MasterTransport& transport); virtual ~SettingsLayout(); @@ -109,10 +110,10 @@ namespace musik { musik::core::ILibraryPtr library; musik::core::IIndexer* indexer; + musik::core::sdk::IPlaybackService& playback; musik::glue::audio::MasterTransport& transport; - std::shared_ptr libraryPrefs; - std::shared_ptr playbackPrefs; + std::shared_ptr prefs; std::shared_ptr localeDropdown; std::shared_ptr outputDropdown; @@ -126,6 +127,7 @@ namespace musik { std::shared_ptr dotfileCheckbox; std::shared_ptr syncOnStartupCheckbox; std::shared_ptr removeCheckbox; + std::shared_ptr seekScrubCheckbox; std::shared_ptr browseLabel; std::shared_ptr addedPathsLabel; diff --git a/src/musikbox/app/util/GlobalHotkeys.cpp b/src/musikbox/app/util/GlobalHotkeys.cpp index 04d4982b3..082c406a4 100755 --- a/src/musikbox/app/util/GlobalHotkeys.cpp +++ b/src/musikbox/app/util/GlobalHotkeys.cpp @@ -84,11 +84,11 @@ bool GlobalHotkeys::Handle(const std::string& kn) { return true; } else if (Hotkeys::Is(Hotkeys::SeekBack, kn)) { - musik::glue::playback::SeekBack(this->transport); + musik::glue::playback::SeekBack(this->playback); return true; } else if (Hotkeys::Is(Hotkeys::SeekForward, kn)) { - musik::glue::playback::SeekForward(this->transport); + musik::glue::playback::SeekForward(this->playback); return true; } else if (Hotkeys::Is(Hotkeys::ToggleRepeat, kn)) { diff --git a/src/musikbox/app/window/TrackListView.cpp b/src/musikbox/app/window/TrackListView.cpp index aeef4510e..494e020af 100755 --- a/src/musikbox/app/window/TrackListView.cpp +++ b/src/musikbox/app/window/TrackListView.cpp @@ -72,8 +72,6 @@ static inline milliseconds now() { return duration_cast(system_clock::now().time_since_epoch()); } -static IScrollAdapter::EntryPtr MISSING_ENTRY = IScrollAdapter::EntryPtr(); - TrackListView::TrackListView( PlaybackService& playback, ILibraryPtr library, @@ -89,12 +87,6 @@ TrackListView::TrackListView( this->lastChanged = now(); this->formatter = formatter; this->decorator = decorator; - - if (!MISSING_ENTRY) { - auto e = std::shared_ptr(new SingleLineEntry("track missing")); - e->SetAttrs(COLOR_PAIR(CURSESPP_TEXT_ERROR)); - MISSING_ENTRY = e; - } } TrackListView::~TrackListView() { @@ -395,7 +387,12 @@ IScrollAdapter::EntryPtr TrackListView::Adapter::GetEntry(cursespp::ScrollableWi TrackPtr track = parent.tracks->Get(trackIndex); if (!track) { - return MISSING_ENTRY; + auto entry = std::shared_ptr(new SingleLineEntry("track missing")); + + entry->SetAttrs(COLOR_PAIR(selected + ? CURSESPP_HIGHLIGHTED_ERROR_LIST_ITEM : CURSESPP_TEXT_ERROR)); + + return entry; } int64 attrs = CURSESPP_DEFAULT_COLOR; diff --git a/src/musikbox/app/window/TransportWindow.cpp b/src/musikbox/app/window/TransportWindow.cpp index 0e9bd0621..6714ecfed 100755 --- a/src/musikbox/app/window/TransportWindow.cpp +++ b/src/musikbox/app/window/TransportWindow.cpp @@ -315,7 +315,7 @@ TransportWindow::TransportWindow(musik::core::audio::PlaybackService& playback) this->playback.ModeChanged.connect(this, &TransportWindow::OnPlaybackModeChanged); this->playback.Shuffled.connect(this, &TransportWindow::OnPlaybackShuffled); this->playback.VolumeChanged.connect(this, &TransportWindow::OnTransportVolumeChanged); - this->transport.TimeChanged.connect(this, &TransportWindow::OnTransportTimeChanged); + this->playback.TimeChanged.connect(this, &TransportWindow::OnTransportTimeChanged); this->paused = false; this->lastTime = DEFAULT_TIME; } @@ -361,11 +361,11 @@ bool TransportWindow::KeyPress(const std::string& kn) { } else if (this->focus == FocusTime) { if (inc(kn)) { - playback::SeekForward(this->transport); + playback::SeekForward(this->playback); return true; } else if (dec(kn)) { - playback::SeekBack(this->transport); + playback::SeekBack(this->playback); return true; } } @@ -557,7 +557,7 @@ void TransportWindow::Update(TimeMode timeMode) { if (timeMode == TimeSmooth) { double smoothedTime = this->lastTime += 1.0f; /* 1000 millis */ - double actualTime = transport.Position(); + double actualTime = playback.GetPosition(); if (paused || stopped || fabs(smoothedTime - actualTime) > TIME_SLOP) { smoothedTime = actualTime; @@ -569,7 +569,7 @@ void TransportWindow::Update(TimeMode timeMode) { secondsCurrent = (int) round(smoothedTime); } else if (timeMode == TimeSync) { - this->lastTime = transport.Position(); + this->lastTime = playback.GetPosition(); secondsCurrent = (int) round(this->lastTime); } diff --git a/src/musikbox/cursespp/Colors.cpp b/src/musikbox/cursespp/Colors.cpp index cf2e49825..cc4115c79 100755 --- a/src/musikbox/cursespp/Colors.cpp +++ b/src/musikbox/cursespp/Colors.cpp @@ -441,6 +441,11 @@ struct Theme { CURSESPP_HIGHLIGHTED_SELECTED_LIST_ITEM, listActiveHighlightedForeground.Id(mode, COLOR_BLACK), listActiveHighlightedBackground.Id(mode, COLOR_YELLOW)); + + init_pair( + CURSESPP_HIGHLIGHTED_ERROR_LIST_ITEM, + textError.Id(mode, COLOR_RED), + listHighlightedBackground.Id(mode, COLOR_GREEN)); } /* main */ diff --git a/src/musikbox/cursespp/Colors.h b/src/musikbox/cursespp/Colors.h index c0d5766e9..d117c2b59 100755 --- a/src/musikbox/cursespp/Colors.h +++ b/src/musikbox/cursespp/Colors.h @@ -40,33 +40,34 @@ #define CURSESPP_SELECTED_LIST_ITEM 1 #define CURSESPP_HIGHLIGHTED_LIST_ITEM 2 -#define CURSESPP_HIGHLIGHTED_SELECTED_LIST_ITEM 3 -#define CURSESPP_LIST_ITEM_HEADER 4 -#define CURSESPP_LIST_ITEM_HIGHLIGHTED_HEADER 5 +#define CURSESPP_HIGHLIGHTED_ERROR_LIST_ITEM 3 +#define CURSESPP_HIGHLIGHTED_SELECTED_LIST_ITEM 4 +#define CURSESPP_LIST_ITEM_HEADER 5 +#define CURSESPP_LIST_ITEM_HIGHLIGHTED_HEADER 6 -#define CURSESPP_DEFAULT_CONTENT_COLOR 6 -#define CURSESPP_DEFAULT_FRAME_COLOR 7 -#define CURSESPP_FOCUSED_FRAME_COLOR 8 +#define CURSESPP_DEFAULT_CONTENT_COLOR 7 +#define CURSESPP_DEFAULT_FRAME_COLOR 8 +#define CURSESPP_FOCUSED_FRAME_COLOR 9 -#define CURSESPP_TEXT_DEFAULT 9 -#define CURSESPP_TEXT_DISABLED 10 -#define CURSESPP_TEXT_FOCUSED 11 -#define CURSESPP_TEXT_ACTIVE 12 -#define CURSESPP_TEXT_WARNING 13 -#define CURSESPP_TEXT_ERROR 14 -#define CURSESPP_TEXT_HIDDEN 15 +#define CURSESPP_TEXT_DEFAULT 10 +#define CURSESPP_TEXT_DISABLED 11 +#define CURSESPP_TEXT_FOCUSED 12 +#define CURSESPP_TEXT_ACTIVE 13 +#define CURSESPP_TEXT_WARNING 14 +#define CURSESPP_TEXT_ERROR 15 +#define CURSESPP_TEXT_HIDDEN 16 -#define CURSESPP_BUTTON_NORMAL 16 -#define CURSESPP_BUTTON_HIGHLIGHTED 17 +#define CURSESPP_BUTTON_NORMAL 17 +#define CURSESPP_BUTTON_HIGHLIGHTED 18 -#define CURSESPP_SHORTCUT_ROW_NORMAL 18 -#define CURSESPP_SHORTCUT_ROW_FOCUSED 19 +#define CURSESPP_SHORTCUT_ROW_NORMAL 19 +#define CURSESPP_SHORTCUT_ROW_FOCUSED 20 -#define CURSESPP_OVERLAY_FRAME 20 -#define CURSESPP_OVERLAY_CONTENT 21 -#define CURSESPP_OVERLAY_INPUT_FRAME 22 +#define CURSESPP_OVERLAY_FRAME 21 +#define CURSESPP_OVERLAY_CONTENT 22 +#define CURSESPP_OVERLAY_INPUT_FRAME 23 -#define CURSESPP_BANNER 23 +#define CURSESPP_BANNER 24 namespace cursespp { class Colors { diff --git a/src/musikbox/data/locales/en_US.json b/src/musikbox/data/locales/en_US.json index af933d5e5..fd6f1c990 100644 --- a/src/musikbox/data/locales/en_US.json +++ b/src/musikbox/data/locales/en_US.json @@ -33,6 +33,7 @@ "settings_first_run_dialog_body": "add some directories that contain music files, then press '%s' to show the library view and start listening!\n\nfor troubleshooting, press '%s' to enter the console view.\n\nother keyboard shorcuts are displayed in the command bar at the bottom of the screen. toggle command mode by pressing 'ESC'.\n\nselect 'ok' to get started.", "settings_needs_restart": "you will need to restart musikbox for this change to take effect.", "settings_selected_locale": "locale: ", + "settings_seek_not_scrub": "seek playback (don't scrub)", "locale_overlay_select_title": "select locale",