Skip to content

Commit

Permalink
Implement burn on Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
vicr123 committed Dec 5, 2024
1 parent 805b73a commit 1fbeba0
Show file tree
Hide file tree
Showing 18 changed files with 560 additions and 139 deletions.
10 changes: 10 additions & 0 deletions application/BurnButton.qml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ Button {
onTriggered: () => {
burn(modelData);
}

Loader {
id: uiLoader
}

Component.onCompleted: () => {
uiLoader.setSource(modelData.qmlFile, {
controller: modelData
});
}
}

onObjectAdded: (index, object) => burnMenu.insertItem(index + 1, object)
Expand Down
17 changes: 17 additions & 0 deletions application/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -340,4 +340,21 @@ ContemporaryWindow {
Item {}
}
}

Repeater {
id: burnRepeater
model: BurnManager.availableBackends
delegate: Item {
required property var modelData
Loader {
id: uiLoader
}

Component.onCompleted: () => {
uiLoader.setSource(modelData.qmlFile, {
controller: modelData
});
}
}
}
}
4 changes: 4 additions & 0 deletions application/PlaylistPane.qml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ Item {
Item {
Layout.fillWidth: true
}
BurnButton {
model: trackList.model
albumName: grandstand2.text
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions libthebeat/burnbackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ BurnBackend::BurnBackend(QObject* parent) :
QString BurnBackend::burn(QStringList files, QString albumName, QQuickWindow* window) {
return {};
}

QUrl BurnBackend::qmlFile() {
return {};
}
2 changes: 2 additions & 0 deletions libthebeat/burnbackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
class LIBTHEBEAT_EXPORT BurnBackend : public QObject {
Q_OBJECT
Q_PROPERTY(QString displayName READ displayName FINAL CONSTANT)
Q_PROPERTY(QUrl qmlFile READ qmlFile FINAL CONSTANT)
public:
explicit BurnBackend(QObject* parent = nullptr);

virtual void burn(QStringList files, QString albumName, QWidget* parentWindow) = 0;
Q_SCRIPTABLE virtual QString burn(QStringList files, QString albumName, QQuickWindow* parentWindow);
virtual QString displayName() = 0;
virtual QUrl qmlFile();

signals:
};
Expand Down
43 changes: 28 additions & 15 deletions plugins/ParanoiaPlugin/paranoiacdcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <pluginmediasource.h>
#include <sourcemanager.h>
#include <QTimer>
#include <QtConcurrent>
#include <QCoroFuture>
#include <statemanager.h>

#include <QAudioSink>
Expand All @@ -17,6 +19,7 @@
#include <cdio++/cdio.hpp>

struct ParanoiaCdControllerPrivate {
QString deviceDescriptor;
PluginMediaSource* source;
ParanoiaPlayer* player = nullptr;
QAudioSink* sink = nullptr;
Expand All @@ -32,15 +35,12 @@ ParanoiaCdController::ParanoiaCdController(QString deviceDescriptor, QWidget* pa
QAbstractListModel(parent) {
d = new ParanoiaCdControllerPrivate();

d->deviceDescriptor = deviceDescriptor;
d->albumName = tr("CD");

d->source = new ParanoiaCdPluginMediaSource(this);
d->source->setIcon(QIcon::fromTheme("media-optical-audio"));

if (d->device.open(deviceDescriptor.toUtf8().constData(), DRIVER_DEVICE)) {
readCd();
}

QAudioFormat format;
format.setChannelCount(2);
format.setSampleRate(44100);
Expand All @@ -49,20 +49,14 @@ ParanoiaCdController::ParanoiaCdController(QString deviceDescriptor, QWidget* pa
d->sink = new QAudioSink(format, this);
d->sinkOutput = d->sink->start();
d->sink->suspend();
connect(d->player, &ParanoiaPlayer::frameAvailable, this, &ParanoiaCdController::feedSink);
connect(d->player, &ParanoiaPlayer::epochChanged, this, [this] {
if (d->sink->state() != QAudio::SuspendedState) {
d->sinkOutput = d->sink->start();
}
});

this->openCd();

QTimer* sinkFeedTimer = new QTimer(this);
sinkFeedTimer->setTimerType(Qt::PreciseTimer);
sinkFeedTimer->setInterval(20);
connect(sinkFeedTimer, &QTimer::timeout, this, &ParanoiaCdController::feedSink);
sinkFeedTimer->start();

StateManager::instance()->sources()->addSource(d->source);
}

ParanoiaCdController::~ParanoiaCdController() {
Expand Down Expand Up @@ -91,18 +85,35 @@ MediaItem* ParanoiaCdController::mediaItem(int row) {
return item;
}

void ParanoiaCdController::readCd() {
// if (!d->device.open(d->disk->interface<BlockInterface>()->blockName().toUtf8().constData(), DRIVER_DEVICE)) return;
QCoro::Task<> ParanoiaCdController::openCd() {
auto deviceOpen = co_await QtConcurrent::run([](CdioDevice * device, QString deviceDescriptor) {
return device->open(deviceDescriptor.toUtf8().constData(), DRIVER_DEVICE);
}, &d->device, d->deviceDescriptor);

if (deviceOpen) {
readCd();
}
}

void ParanoiaCdController::readCd() {
auto firstTrack = d->device.getFirstTrackNum();
auto lastTrack = d->device.getLastTrackNum();
// Probably not an audio CD so we don't really care
if (firstTrack == lastTrack) return;

// auto drive = d->disk->interface<BlockInterface>()->drive();
for (auto i = firstTrack; i <= lastTrack; i++) {
d->trackInfo.append(ParanoiaTrackInfoPtr(new ParanoiaTrackInfo(i - 1)));
}

this->setCdTextMetadata();
d->player = new ParanoiaPlayer(&d->device, this);
connect(d->player, &ParanoiaPlayer::frameAvailable, this, &ParanoiaCdController::feedSink);
connect(d->player, &ParanoiaPlayer::epochChanged, this, [this] {
if (d->sink->state() != QAudio::SuspendedState) {
d->sinkOutput = d->sink->start();
}
});
this->setupMusicBrainzClient();
if (d->musicBrainzClient) {
connect(d->musicBrainzClient, &MusicBrainzClient::albumNameChanged, this, [this] {
Expand All @@ -128,10 +139,12 @@ void ParanoiaCdController::readCd() {
}

emit dataChanged(index(0), index(rowCount() - 1));

StateManager::instance()->sources()->addSource(d->source);
}

void ParanoiaCdController::feedSink() {
while (d->sink->bytesFree() >= 2342 && d->player->isFrameAvailable() && d->sink->state() != QAudio::SuspendedState) {
while (d->sink->bytesFree() >= 2342 && d->player && d->player->isFrameAvailable() && d->sink->state() != QAudio::SuspendedState) {
d->sinkOutput->write(d->player->nextFrame(1));
}
}
Expand Down
1 change: 1 addition & 0 deletions plugins/ParanoiaPlugin/paranoiacdcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class ParanoiaCdController : public QAbstractListModel {
private:
ParanoiaCdControllerPrivate* d;

QCoro::Task<> openCd();
void readCd();
void feedSink();
void setCdTextMetadata();
Expand Down
1 change: 1 addition & 0 deletions plugins/WinIntegration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ qt_add_qml_module(plugin-winintegration
URI com.vicr123.thebeat.plugin.winintegration
VERSION 1.0
QML_FILES
WinBurnPopover.qml
WinCdPane.qml
NO_CACHEGEN
)
Expand Down
98 changes: 98 additions & 0 deletions plugins/WinIntegration/WinBurnPopover.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import QtQuick 2.15
import QtQuick.Controls
import QtQuick.Layouts
import com.vicr123.Contemporary
import Contemporary

Drawer {
id: popover
property var controller
width: window.width
height: Math.max(window.height - 300, 300)
edge: Qt.BottomEdge
interactive: false
visible: controller.visible

Grandstand {
id: grandstand
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right

backButtonVisible: true
text: qsTr("Burn %1").arg(Contemporary.quoteString(controller.albumName))

onBackButtonClicked: controller.close()
}

Pager {
anchors.topMargin: 3
anchors.top: grandstand.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
currentIndex: controller.imageReady ? 1 : 0

Item {
BusyIndicator {
anchors.centerIn: parent
}
}

ColumnLayout {
GroupBox {
title: qsTr("Burn Options")
implicitWidth: 600

Layout.alignment: Qt.AlignHCenter | Qt.AlignTop

GridLayout {
anchors.fill: parent
columns: 2
rows: 2

Label {
Layout.row: 0
Layout.column: 0
text: qsTr("Album Name")
}

TextField {
id: albumNameField
Layout.fillWidth: true
Layout.row: 0
Layout.column: 1
placeholderText: qsTr("Album Name")
text: controller.albumName
onTextEdited: () => {
controller.albumName = albumNameField.text;
}
}
}
}

Admonition {
implicitWidth: 600
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
visible: !!controller.admonition
title: qsTr("Heads up!")
text: controller.admonition
severity: controller.admonitionIsError ? Admonition.Severity.Error : Admonition.Severity.Warning
}

Button {
implicitWidth: 600
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
text: qsTr("Burn")
icon.name: "tools-media-optical-burn"
enabled: !controller.admonitionIsError

onClicked: () => controller.startBurn()
}

Item {
Layout.fillHeight: true
}
}
}
}
Loading

0 comments on commit 1fbeba0

Please sign in to comment.