6#include <private/qplatformaudiodevices_p.h>
7#include <private/qplatformmediaintegration_p.h>
11#include <private/qstdweb_p.h>
12#include <QtCore/QIODevice>
20using namespace Qt::Literals;
27 m_durationTimer.reset(
new QElapsedTimer());
28 QPlatformMediaIntegration::instance()->audioDevices();
30 m_jsMediaRecorderDevice.reset(
new JsMediaRecorder());
32 connect(m_jsMediaRecorderDevice.get(), &JsMediaRecorder::started,
this,
35 m_durationTimer->start();
36 emit stateChanged(QMediaRecorder::RecordingState);
39 connect(m_jsMediaRecorderDevice.get(), &JsMediaRecorder::stopped,
this,
41 m_isRecording =
false;
42 m_durationMs = m_durationTimer->elapsed();
43 emit durationChanged(m_durationMs);
45 m_durationTimer->invalidate();
46 emit stateChanged(QMediaRecorder::StoppedState);
49 connect(m_jsMediaRecorderDevice.get(), &JsMediaRecorder::paused,
this,
51 m_isRecording =
false;
52 m_durationMs = m_durationTimer->elapsed();
53 emit durationChanged(m_durationMs);
55 m_durationTimer->invalidate();
56 emit stateChanged(QMediaRecorder::PausedState);
59 connect(m_jsMediaRecorderDevice.get(), &JsMediaRecorder::resumed,
this,
62 m_durationTimer->start();
65 connect(m_jsMediaRecorderDevice.get(), &JsMediaRecorder::streamError,
this,
66 [
this](QMediaRecorder::Error errorCode,
const QString &errorMessage) {
67 updateError(errorCode, errorMessage);
68 emit stateChanged(state());
72QWasmMediaRecorder::~QWasmMediaRecorder()
74 if (m_outputTarget->isOpen())
75 m_outputTarget->close();
77 if (m_jsMediaRecorderDevice->isOpen()) {
78 qWarning() <<
" bytes still available" << m_jsMediaRecorderDevice->bytesAvailable();
79 m_jsMediaRecorderDevice->close();
82 if (!m_mediaRecorder.isNull()) {
83 m_mediaStreamDataAvailable.reset(
nullptr);
84 m_mediaStreamStopped.reset(
nullptr);
85 m_mediaStreamError.reset(
nullptr);
86 m_mediaStreamStart.reset(
nullptr);
92 return location.isValid() && (location.isLocalFile() || location.isRelative());
97 return m_jsMediaRecorderDevice->currentState();
110 m_mediaSettings = settings;
111 if (!m_jsMediaRecorderDevice->open(QIODeviceBase::ReadOnly)) {
112 qWarning() <<
"m_jsMediaRecorderDevice is not open";
117 m_jsMediaRecorderDevice->startStreaming();
123 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"could not find MediaRecorder";
126 m_jsMediaRecorderDevice->pauseStream();
132 qCDebug(qWasmMediaRecorder)<< Q_FUNC_INFO <<
"could not find MediaRecorder";
135 m_jsMediaRecorderDevice->resumeStream();
141 qCDebug(qWasmMediaRecorder)<< Q_FUNC_INFO <<
"could not find MediaRecorder";
144 m_jsMediaRecorderDevice->stopStream();
154 return m_session && m_session->camera();
160 emscripten::val navigator = emscripten::val::global(
"navigator");
161 emscripten::val mediaDevices = navigator[
"mediaDevices"];
163 if (mediaDevices.isNull() || mediaDevices.isUndefined()) {
164 qCDebug(qWasmMediaRecorder) <<
"MediaDevices are undefined or null";
170 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO << m_session;
172 emscripten::val stream = emscripten::val::undefined();
174 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"has camera";
178 m_jsMediaRecorderDevice->setNeedsCamera(
true);
180 if (m_video.isNull() || m_video.isUndefined()) {
181 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"video element not found";
185 stream = m_video[
"srcObject"];
186 if (stream.isNull() || stream.isUndefined()) {
187 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"Video input stream not found";
192 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"has audio";
193 stream =
static_cast<QWasmAudioInput *>(m_session->audioInput())->mediaStream();
195 if (stream.isNull() || stream.isUndefined()) {
196 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"Audio input stream not found";
199 m_jsMediaRecorderDevice->setNeedsAudio(
true);
201 if (stream.isNull() || stream.isUndefined()) {
202 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"No input stream found";
205 if (!m_jsMediaRecorderDevice->open(QIODeviceBase::ReadOnly)) {
206 qWarning() <<
"m_jsMediaRecorderDevice is not open";
210 QObject::connect(m_jsMediaRecorderDevice.get(), &JsMediaRecorder::readyRead,
this, [
this]() {
212 if (m_jsMediaRecorderDevice->bytesAvailable()) {
214 QByteArray mediaData = m_jsMediaRecorderDevice->read(m_jsMediaRecorderDevice->bytesAvailable());
216 m_durationMs = m_durationTimer->elapsed();
217 if (m_outputTarget->isOpen())
218 m_outputTarget->write(mediaData, mediaData.length());
220 if (m_durationMs > 0) {
221 emit durationChanged(m_durationMs);
222 qCDebug(qWasmMediaRecorder) <<
"duration changed" << m_durationMs;
227 m_jsMediaRecorderDevice->setStream(stream);
237 m_hasMediaSettings =
true;
244 setTrackContraints(m_mediaSettings, stream);
250void QWasmMediaRecorder::setTrackContraints(QMediaEncoderSettings &settings, emscripten::val stream)
252 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO
253 << settings.audioSampleRate()
254 << settings.videoResolution();
256 if (stream.isUndefined() || stream.isNull()) {
257 qCDebug(qWasmMediaRecorder)<< Q_FUNC_INFO <<
"could not find MediaStream";
261 emscripten::val navigator = emscripten::val::global(
"navigator");
262 emscripten::val mediaDevices = navigator[
"mediaDevices"];
265 emscripten::val allConstraints = mediaDevices.call<emscripten::val>(
"getSupportedConstraints");
268 emscripten::val videoParams = emscripten::val::object();
269 emscripten::val constraints = emscripten::val::object();
270 videoParams.set(
"resizeMode",std::string(
"crop-and-scale"));
273 if (settings.videoFrameRate() > 0)
274 videoParams.set(
"frameRate", emscripten::val(settings.videoFrameRate()));
275 if (settings.videoResolution().height() > 0)
276 videoParams.set(
"height",
277 emscripten::val(settings.videoResolution().height()));
278 if (settings.videoResolution().width() > 0)
279 videoParams.set(
"width", emscripten::val(settings.videoResolution().width()));
281 constraints.set(
"video", videoParams);
284 emscripten::val audioParams = emscripten::val::object();
285 if (settings.audioSampleRate() > 0)
286 audioParams.set(
"sampleRate", emscripten::val(settings.audioSampleRate()));
287 if (settings.audioBitRate() > 0)
288 audioParams.set(
"sampleSize", emscripten::val(settings.audioBitRate()));
289 if (settings.audioChannelCount() > 0)
290 audioParams.set(
"channelCount", emscripten::val(settings.audioChannelCount()));
292 constraints.set(
"audio", audioParams);
294 if (hasCamera() && stream[
"active"].as<
bool>()) {
295 emscripten::val videoTracks = emscripten::val::undefined();
296 videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
297 if (videoTracks.isNull() || videoTracks.isUndefined()) {
298 qCDebug(qWasmMediaRecorder) <<
"no video tracks";
301 if (videoTracks[
"length"].as<
int>() > 0) {
303 qstdweb::Promise::make(videoTracks[0], QStringLiteral(
"applyConstraints"),
305 [
this](emscripten::val result) {
310 [
this](emscripten::val theError) {
311 qCDebug(qWasmMediaRecorder)
312 << theError[
"code"].as<
int>()
313 << QString::fromStdString(theError[
"message"].as<std::string>());
314 updateError(QMediaRecorder::ResourceError,
315 QString::fromStdString(theError[
"message"].as<std::string>()));
317 .finallyFunc = []() {},
328 m_jsMediaRecorderDevice->startStreaming();
333 QString m_targetFileName = outputLocation().toLocalFile();
335 QString suffix = m_mediaSettings.mimeType().preferredSuffix();
338 if (!m_mediaSettings.mimeType().isValid()) {
339 qWarning() <<
"mimetype not valid, using m4v";
340 suffix = QStringLiteral(
".m4v");
342 if (m_targetFileName.isEmpty()) {
343 m_targetFileName = QDir::homePath() + u"/tmp."_s + suffix;
344 QPlatformMediaRecorder::setOutputLocation(m_targetFileName);
347 m_outputTarget =
new QFile(m_targetFileName,
this);
348 if (!m_outputTarget->open(QIODevice::WriteOnly)) {
349 qWarning() <<
"target file is not writable";
QWasmVideoOutput * cameraOutput()
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")