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
This commit is contained in:
casey langen 2017-03-28 09:48:32 -07:00
parent 2dac91c429
commit bf1af609b2
40 changed files with 1085 additions and 111 deletions

View File

@ -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 <string>
#include <Windows.h>
#include <dbt.h>
#include <map>
#define CLASS_NAME L"CddaDataModelEventClass"
#define WINDOW_NAME L"CddaDataModelWindow"
using AudioDiscPtr = CddaDataModel::AudioDiscPtr;
using DiscTrackPtr = CddaDataModel::DiscTrackPtr;
static std::map<HWND, CddaDataModel*> 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<DEV_BROADCAST_HDR*>(lParam);
if (info->dbch_devicetype == DBT_DEVTYP_VOLUME) {
DEV_BROADCAST_VOLUME* volumeInfo = reinterpret_cast<DEV_BROADCAST_VOLUME*>(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<AudioDiscPtr> CddaDataModel::GetAudioDiscs() {
std::vector<AudioDiscPtr> 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<AudioDisc>(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<DiscTrack>(
toc.TrackData[j], driveLetter, j, duration));
}
if (audioDisc->GetTrackCount()) {
audioDisc->SetLeadout(std::make_shared<DiscTrack>(
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);
}

View File

@ -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 <vector>
#include <memory>
#include <thread>
#include <mutex>
#include <set>
#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<DiscTrack>;
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<DiscTrackPtr> tracks;
DiscTrackPtr leadout;
char driveLetter;
};
using AudioDiscPtr = std::shared_ptr<AudioDisc>;
static CddaDataModel& Instance() {
return Instance(true);
}
static void Shutdown() {
Instance(false).StopWindowThread();
}
CddaDataModel& operator=(const CddaDataModel&) = delete;
CddaDataModel(const CddaDataModel&) = delete;
std::vector<AudioDiscPtr> 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<std::thread>;
using Mutex = std::recursive_mutex;
using Lock = std::unique_lock<Mutex>;
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<EventListener*> listeners;
};

View File

@ -34,19 +34,23 @@
#include "stdafx.h"
#include "CddaDataStream.h"
#include <algorithm>
#include <string>
#include <set>
#include <mutex>
#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<std::mutex> 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;
}

View File

@ -38,6 +38,8 @@
#include "devioctl.h"
#include <string>
#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
};

View File

@ -33,15 +33,17 @@
//////////////////////////////////////////////////////////////////////////////
#include <core/sdk/IDataStreamFactory.h>
#include <core/sdk/IMetadataReader.h>
#include <string>
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();
};

View File

@ -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 <core/sdk/IIndexerNotifier.h>
#include <string>
#include <sstream>
#include <set>
using namespace musik::core::sdk;
using DiscList = std::vector<CddaDataModel::AudioDiscPtr>;
using DiscIdList = std::set<std::string>;
static std::mutex globalSinkMutex;
static musik::core::sdk::IIndexerNotifier* notifier;
extern "C" __declspec(dllexport) void SetIndexerNotifier(musik::core::sdk::IIndexerNotifier* notifier) {
std::unique_lock<std::mutex> lock(globalSinkMutex);
::notifier = notifier;
}
static std::vector<std::string> tokenize(const std::string& str, char delim = '/') {
std::vector<std::string> 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<std::string> 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<std::mutex> 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<std::string>()(PLUGIN_NAME);
}

View File

@ -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 <core/sdk/IIndexerSource.h>
#include "CddaDataModel.h"
#include <functional>
#include <set>
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<std::string> discIds;
std::vector<CddaDataModel::AudioDiscPtr> discs;
};

View File

@ -99,18 +99,22 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="CddaDataModel.cpp" />
<ClCompile Include="CddaDataStream.cpp" />
<ClCompile Include="CddaDataStreamFactory.cpp" />
<ClCompile Include="CddaDecoder.cpp" />
<ClCompile Include="cddadecoder_plugin.cpp" />
<ClCompile Include="CddaDecoderFactory.cpp" />
<ClCompile Include="CddaIndexerSource.cpp" />
<ClCompile Include="stdafx.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="CddaDataModel.h" />
<ClInclude Include="CddaDataStream.h" />
<ClInclude Include="CddaDataStreamFactory.h" />
<ClInclude Include="CddaDecoder.h" />
<ClInclude Include="CddaDecoderFactory.h" />
<ClInclude Include="CddaIndexerSource.h" />
<ClInclude Include="devioctl.h" />
<ClInclude Include="ntddcdrm.h" />
<ClInclude Include="ntddstor.h" />

View File

@ -31,6 +31,12 @@
<ClCompile Include="CddaDataStreamFactory.cpp">
<Filter>plugin</Filter>
</ClCompile>
<ClCompile Include="CddaIndexerSource.cpp">
<Filter>plugin</Filter>
</ClCompile>
<ClCompile Include="CddaDataModel.cpp">
<Filter>plugin</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">
@ -57,5 +63,11 @@
<ClInclude Include="CddaDataStream.h">
<Filter>plugin</Filter>
</ClInclude>
<ClInclude Include="CddaIndexerSource.h">
<Filter>plugin</Filter>
</ClInclude>
<ClInclude Include="CddaDataModel.h">
<Filter>plugin</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -36,17 +36,22 @@
#include "CddaDecoderFactory.h"
#include "CddaDataStreamFactory.h"
#include "CddaIndexerSource.h"
#include <core/sdk/constants.h>
#include <core/sdk/IPlugin.h>
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();
}

View File

@ -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 <shlwapi.h>
#include <Windows.h>

View File

@ -42,7 +42,7 @@
<_ProjectFileVersion>14.0.25123.0</_ProjectFileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>$(SolutionDir)bin\$(Configuration)\plugins</OutDir>
<OutDir>$(SolutionDir)bin\$(Configuration)\plugins\</OutDir>
<IntDir>./obj/$(Configuration)\</IntDir>
<LinkIncremental>true</LinkIncremental>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
@ -50,7 +50,7 @@
<CodeAnalysisRuleAssemblies />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>$(SolutionDir)bin\$(Configuration)\plugins</OutDir>
<OutDir>$(SolutionDir)bin\$(Configuration)\plugins\</OutDir>
<IntDir>./obj/$(Configuration)\</IntDir>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<CodeAnalysisRules />

View File

@ -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);
}
}

View File

@ -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 */

View File

@ -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<IStream> IStreamPtr;

View File

@ -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<std::recursive_mutex> 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<std::recursive_mutex> 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

View File

@ -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;
};

View File

@ -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();

View File

@ -36,6 +36,7 @@
#include <core/config.h>
#include <core/audio/IStream.h>
#include <core/sdk/constants.h>
#include <core/sdk/IOutput.h>
#include <core/sdk/IBufferProvider.h>
@ -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:

View File

@ -36,6 +36,7 @@
#include "Stream.h"
#include "Streams.h"
#include <core/sdk/constants.h>
#include <core/debug.h>
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;
}

View File

@ -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;

View File

@ -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;

View File

@ -832,4 +832,3 @@ void Indexer::ScheduleRescan(IIndexerSource* source) {
this->Schedule(SyncType::Sources, source);
}
}

View File

@ -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 {

View File

@ -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;
};
} } }

View File

@ -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;
};
} } }

View File

@ -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";

View File

@ -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";

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -35,14 +35,15 @@
#pragma once
#include <core/audio/ITransport.h>
#include <core/sdk/IPlaybackService.h>
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);
}
}

View File

@ -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));

View File

@ -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

View File

@ -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<musik::core::Preferences> libraryPrefs;
std::shared_ptr<musik::core::Preferences> playbackPrefs;
std::shared_ptr<musik::core::Preferences> prefs;
std::shared_ptr<cursespp::TextLabel> localeDropdown;
std::shared_ptr<cursespp::TextLabel> outputDropdown;
@ -126,6 +127,7 @@ namespace musik {
std::shared_ptr<cursespp::Checkbox> dotfileCheckbox;
std::shared_ptr<cursespp::Checkbox> syncOnStartupCheckbox;
std::shared_ptr<cursespp::Checkbox> removeCheckbox;
std::shared_ptr<cursespp::Checkbox> seekScrubCheckbox;
std::shared_ptr<cursespp::TextLabel> browseLabel;
std::shared_ptr<cursespp::TextLabel> addedPathsLabel;

View File

@ -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)) {

View File

@ -72,8 +72,6 @@ static inline milliseconds now() {
return duration_cast<milliseconds>(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<SingleLineEntry>(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<SingleLineEntry>(new SingleLineEntry("track missing"));
entry->SetAttrs(COLOR_PAIR(selected
? CURSESPP_HIGHLIGHTED_ERROR_LIST_ITEM : CURSESPP_TEXT_ERROR));
return entry;
}
int64 attrs = CURSESPP_DEFAULT_COLOR;

View File

@ -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);
}

View File

@ -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 */

View File

@ -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 {

View File

@ -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",