setup electron app with boilerplate
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
#include "mediahandler.h"
|
||||
#include <QPermission>
|
||||
#include <QMediaCaptureSession>
|
||||
#include <QApplication>
|
||||
#include <QCameraDevice>
|
||||
#include <QCameraFormat>
|
||||
#include <QMediaRecorder>
|
||||
#include <QMediaFormat>
|
||||
#include <QMimeType>
|
||||
#include <QAudioInput>
|
||||
#include <QCamera>
|
||||
#include <QUrl>
|
||||
|
||||
MediaHandler::MediaHandler(QObject *parent) : QObject{parent},
|
||||
m_captureSession{nullptr},
|
||||
m_audioInput{nullptr},
|
||||
m_camera{nullptr},
|
||||
m_videoSink{nullptr},
|
||||
m_recorder{nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
MediaHandler::~MediaHandler()
|
||||
{
|
||||
}
|
||||
|
||||
MediaHandler::State MediaHandler::state() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
MediaHandler::CaptureMode MediaHandler::captureMode() const
|
||||
{
|
||||
return m_captureMode;
|
||||
}
|
||||
|
||||
QVideoSink *MediaHandler::videoSink() const
|
||||
{
|
||||
return m_videoSink;
|
||||
}
|
||||
|
||||
void MediaHandler::startPreview(QVideoSink *sink)
|
||||
{
|
||||
if (m_state != OFF)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_pendingRecord = false;
|
||||
startCapture(sink);
|
||||
}
|
||||
|
||||
void MediaHandler::startRecording(QVideoSink *sink)
|
||||
{
|
||||
if (m_state == RECORDING || m_state == PREPARING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_state == ON)
|
||||
{
|
||||
m_cancelledRecording = false;
|
||||
m_recorder->record();
|
||||
return;
|
||||
}
|
||||
|
||||
// OFF
|
||||
m_pendingRecord = true;
|
||||
startCapture(sink);
|
||||
}
|
||||
|
||||
void MediaHandler::startCapture(QVideoSink *sink)
|
||||
{
|
||||
setState(PREPARING);
|
||||
|
||||
// microphone permission (always needed)
|
||||
QMicrophonePermission microphonePermission;
|
||||
switch (qApp->checkPermission(microphonePermission))
|
||||
{
|
||||
case Qt::PermissionStatus::Undetermined:
|
||||
qApp->requestPermission(microphonePermission, this, [this, sink]() {
|
||||
startCapture(sink);
|
||||
});
|
||||
return;
|
||||
case Qt::PermissionStatus::Denied:
|
||||
qDebug("Microphone permission is not granted!");
|
||||
setState(OFF);
|
||||
emit micPermissionDenied();
|
||||
return;
|
||||
case Qt::PermissionStatus::Granted:
|
||||
qDebug("Mic permission granted.");
|
||||
break;
|
||||
}
|
||||
|
||||
// camera permission (skip for AUDIO_ONLY)
|
||||
if (m_captureMode != AUDIO_ONLY)
|
||||
{
|
||||
QCameraPermission cameraPermission;
|
||||
switch (qApp->checkPermission(cameraPermission))
|
||||
{
|
||||
case Qt::PermissionStatus::Undetermined:
|
||||
qApp->requestPermission(cameraPermission, this, [this, sink]() {
|
||||
startCapture(sink);
|
||||
});
|
||||
return;
|
||||
case Qt::PermissionStatus::Denied:
|
||||
qDebug("Camera permission is not granted!");
|
||||
setState(OFF);
|
||||
emit cameraPermissionDenied();
|
||||
return;
|
||||
case Qt::PermissionStatus::Granted:
|
||||
qDebug("Camera permission granted.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create capture session and audio input
|
||||
if (!m_captureSession)
|
||||
{
|
||||
m_captureSession = new QMediaCaptureSession{this};
|
||||
}
|
||||
|
||||
// TODO: handle QMediaDevices:audioInputsChanged()
|
||||
if (!m_audioInput)
|
||||
{
|
||||
m_audioInput = new QAudioInput{this};
|
||||
m_captureSession->setAudioInput(m_audioInput);
|
||||
}
|
||||
|
||||
// Camera setup (skip for AUDIO_ONLY)
|
||||
// TODO: handle QMediaDevices:videoInputsChanged()
|
||||
if (m_captureMode != AUDIO_ONLY)
|
||||
{
|
||||
if (!m_camera)
|
||||
{
|
||||
QCameraDevice cameraDevice = QMediaDevices::defaultVideoInput();
|
||||
if (cameraDevice.videoFormats().empty())
|
||||
{
|
||||
setState(OFF);
|
||||
emit errorOccurred("Unable to find suitable camera formats");
|
||||
return;
|
||||
}
|
||||
QCameraFormat cameraFormat = cameraDevice.videoFormats().at(0);
|
||||
for (auto format : cameraDevice.videoFormats())
|
||||
{
|
||||
qDebug() << "Format: " << format.resolution();
|
||||
qDebug() << "framerate: " << format.minFrameRate() << " " << format.maxFrameRate();
|
||||
if (format.isNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (format.resolution().width() == 1280 && format.resolution().height() == 720)
|
||||
{
|
||||
cameraFormat = format;
|
||||
}
|
||||
}
|
||||
|
||||
m_camera = new QCamera(cameraDevice);
|
||||
m_camera->setCameraFormat(cameraFormat);
|
||||
connect(m_camera, &QCamera::activeChanged, this, [this](bool active) {
|
||||
if (active)
|
||||
{
|
||||
setState(ON);
|
||||
if (m_pendingRecord)
|
||||
{
|
||||
m_pendingRecord = false;
|
||||
m_cancelledRecording = false;
|
||||
m_recorder->record();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_camera, &QCamera::errorOccurred, this, [this](QCamera::Error error, const QString &message) {
|
||||
if (error != QCamera::NoError)
|
||||
{
|
||||
stop();
|
||||
emit errorOccurred(message);
|
||||
}
|
||||
});
|
||||
m_captureSession->setCamera(m_camera);
|
||||
}
|
||||
|
||||
m_videoSink = sink;
|
||||
m_captureSession->setVideoSink(m_videoSink);
|
||||
}
|
||||
else
|
||||
{
|
||||
// AUDIO_ONLY: no camera
|
||||
m_captureSession->setCamera(nullptr);
|
||||
m_captureSession->setVideoSink(nullptr);
|
||||
m_videoSink = nullptr;
|
||||
}
|
||||
|
||||
// Recorder setup
|
||||
if (!m_recorder)
|
||||
{
|
||||
QMediaFormat mediaFormat{QMediaFormat::MPEG4};
|
||||
mediaFormat.setAudioCodec(QMediaFormat::AudioCodec::AAC);
|
||||
if (m_captureMode != AUDIO_ONLY)
|
||||
{
|
||||
mediaFormat.setVideoCodec(QMediaFormat::VideoCodec::H264);
|
||||
}
|
||||
if (!mediaFormat.isSupported(QMediaFormat::Encode))
|
||||
{
|
||||
setState(OFF);
|
||||
emit errorOccurred("Unsupported codec");
|
||||
return;
|
||||
}
|
||||
|
||||
m_recorder = new QMediaRecorder(this);
|
||||
m_captureSession->setRecorder(m_recorder);
|
||||
m_recorder->setQuality(QMediaRecorder::HighQuality);
|
||||
m_recorder->setAutoStop(true);
|
||||
m_recorder->setMediaFormat(mediaFormat);
|
||||
|
||||
connect(m_recorder, &QMediaRecorder::recorderStateChanged, this, [this](QMediaRecorder::RecorderState state) {
|
||||
switch (state)
|
||||
{
|
||||
case QMediaRecorder::StoppedState:
|
||||
if (m_state == RECORDING)
|
||||
{
|
||||
setState(ON);
|
||||
}
|
||||
if (!m_cancelledRecording)
|
||||
{
|
||||
qint64 duration = m_recorder->duration();
|
||||
QString mimeType = m_recorder->mediaFormat().mimeType().name();
|
||||
qDebug() << "Recording location " << m_recorder->actualLocation().toString();
|
||||
emit recordingComplete(m_recorder->actualLocation(), duration, mimeType);
|
||||
}
|
||||
m_cancelledRecording = false;
|
||||
break;
|
||||
case QMediaRecorder::PausedState:
|
||||
break;
|
||||
case QMediaRecorder::RecordingState:
|
||||
setState(RECORDING);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (m_captureMode != AUDIO_ONLY)
|
||||
{
|
||||
m_camera->start();
|
||||
}
|
||||
else
|
||||
{
|
||||
// AUDIO_ONLY: no async camera startup, go straight to ON
|
||||
setState(ON);
|
||||
if (m_pendingRecord)
|
||||
{
|
||||
m_pendingRecord = false;
|
||||
m_cancelledRecording = false;
|
||||
m_recorder->record();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MediaHandler::stop()
|
||||
{
|
||||
if (m_state == OFF)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_pendingRecord = false;
|
||||
|
||||
if (m_state == RECORDING && m_recorder)
|
||||
{
|
||||
m_cancelledRecording = true;
|
||||
m_recorder->stop();
|
||||
}
|
||||
|
||||
if (m_camera)
|
||||
{
|
||||
m_camera->stop();
|
||||
delete m_camera;
|
||||
m_camera = nullptr;
|
||||
}
|
||||
|
||||
if (m_captureSession)
|
||||
{
|
||||
m_captureSession->setVideoSink(nullptr);
|
||||
}
|
||||
|
||||
m_videoSink = nullptr;
|
||||
|
||||
if (m_recorder)
|
||||
{
|
||||
delete m_recorder;
|
||||
m_recorder = nullptr;
|
||||
}
|
||||
|
||||
setState(OFF);
|
||||
}
|
||||
|
||||
void MediaHandler::recordStop()
|
||||
{
|
||||
if (m_state != RECORDING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_cancelledRecording = false;
|
||||
m_recorder->stop();
|
||||
}
|
||||
|
||||
void MediaHandler::recordCancel()
|
||||
{
|
||||
if (m_state != RECORDING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_cancelledRecording = true;
|
||||
m_recorder->stop();
|
||||
}
|
||||
|
||||
void MediaHandler::setCaptureMode(CaptureMode newMode)
|
||||
{
|
||||
if (m_captureMode == newMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_captureMode = newMode;
|
||||
emit captureModeChanged();
|
||||
}
|
||||
|
||||
void MediaHandler::setState(State state)
|
||||
{
|
||||
if (m_state == state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_state = state;
|
||||
emit stateChanged();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#ifndef MEDIAHANDLER_H
|
||||
#define MEDIAHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMediaDevices>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QMediaCaptureSession;
|
||||
class QVideoSink;
|
||||
class QAudioInput;
|
||||
class QCamera;
|
||||
class QMediaRecorder;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
// TODO: create simple structs to represent camera and audio input devices
|
||||
|
||||
/// MediaHandler allows you to preview and capture video, audio, and screen
|
||||
class MediaHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MediaHandler(QObject *parent = nullptr);
|
||||
~MediaHandler();
|
||||
|
||||
enum State
|
||||
{
|
||||
OFF = 0,
|
||||
PREPARING,
|
||||
ON,
|
||||
RECORDING
|
||||
};
|
||||
State state() const;
|
||||
|
||||
enum CaptureMode
|
||||
{
|
||||
AUDIO_ONLY = 0,
|
||||
CAMERA,
|
||||
SCREEN
|
||||
};
|
||||
CaptureMode captureMode() const;
|
||||
|
||||
QVideoSink *videoSink() const;
|
||||
|
||||
// QList<CameraDevices>
|
||||
// QList<AudioDevices>
|
||||
|
||||
public slots:
|
||||
/// Only impacts the next time start is called
|
||||
void setCaptureMode(CaptureMode);
|
||||
|
||||
/// Start preview: OFF → ON
|
||||
void startPreview(QVideoSink *sink = nullptr);
|
||||
|
||||
/// Start recording: OFF → ON → RECORDING (auto-records when ready)
|
||||
/// or ON → RECORDING (immediate)
|
||||
void startRecording(QVideoSink *sink = nullptr);
|
||||
|
||||
/// Clean up resources, will stop all capture, recording, preview
|
||||
void stop();
|
||||
|
||||
void recordCancel();
|
||||
void recordStop();
|
||||
|
||||
// void setCameraDevice();
|
||||
// void setAudioInputDevice();
|
||||
|
||||
signals:
|
||||
/// User has denied mic permission, and must go to settings to enable
|
||||
void micPermissionDenied();
|
||||
/// User has denied camera permission, and must go to settings to enable
|
||||
void cameraPermissionDenied();
|
||||
|
||||
void captureModeChanged();
|
||||
void stateChanged();
|
||||
|
||||
void recordingComplete(const QUrl &absoluteFilePath, qint64 durationMs, const QString &mimeType);
|
||||
|
||||
void errorOccurred(const QString &);
|
||||
|
||||
private:
|
||||
QMediaCaptureSession *m_captureSession;
|
||||
QAudioInput *m_audioInput;
|
||||
QCamera *m_camera;
|
||||
QVideoSink *m_videoSink;
|
||||
QMediaRecorder *m_recorder;
|
||||
QMediaDevices m_devices;
|
||||
|
||||
CaptureMode m_captureMode = CAMERA;
|
||||
State m_state = OFF;
|
||||
void setState(State);
|
||||
|
||||
void startCapture(QVideoSink *sink);
|
||||
|
||||
bool m_pendingRecord = false;
|
||||
bool m_cancelledRecording = false;
|
||||
};
|
||||
|
||||
#endif //MEDIAHANDLER_H
|
||||
Reference in New Issue
Block a user