mirror of
https://github.com/clangen/musikcube.git
synced 2025-02-06 03:39:50 +00:00
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:
parent
2dac91c429
commit
bf1af609b2
366
src/contrib/cddadecoder/CddaDataModel.cpp
Normal file
366
src/contrib/cddadecoder/CddaDataModel.cpp
Normal 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);
|
||||
}
|
139
src/contrib/cddadecoder/CddaDataModel.h
Normal file
139
src/contrib/cddadecoder/CddaDataModel.h
Normal 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;
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
};
|
@ -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();
|
||||
};
|
170
src/contrib/cddadecoder/CddaIndexerSource.cpp
Normal file
170
src/contrib/cddadecoder/CddaIndexerSource.cpp
Normal 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);
|
||||
}
|
71
src/contrib/cddadecoder/CddaIndexerSource.h
Normal file
71
src/contrib/cddadecoder/CddaIndexerSource.h
Normal 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;
|
||||
};
|
@ -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" />
|
||||
|
@ -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>
|
@ -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();
|
||||
}
|
@ -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>
|
@ -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 />
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -832,4 +832,3 @@ void Indexer::ScheduleRescan(IIndexerSource* source) {
|
||||
this->Schedule(SyncType::Sources, source);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
} } }
|
@ -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;
|
||||
};
|
||||
|
||||
} } }
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user