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 m_jsMediaRecorderDevice->close();
81 if (!m_mediaRecorder.isNull()) {
82 m_mediaStreamDataAvailable.reset(
nullptr);
83 m_mediaStreamStopped.reset(
nullptr);
84 m_mediaStreamError.reset(
nullptr);
85 m_mediaStreamStart.reset(
nullptr);
91 return location.isValid() && (location.isLocalFile() || location.isRelative());
96 return m_jsMediaRecorderDevice->currentState();
109 m_mediaSettings = settings;
110 if (!m_jsMediaRecorderDevice->open(QIODeviceBase::ReadOnly)) {
111 qWarning() <<
"m_jsMediaRecorderDevice is not open";
116 m_jsMediaRecorderDevice->startStreaming();
122 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"could not find MediaRecorder";
125 m_jsMediaRecorderDevice->pauseStream();
131 qCDebug(qWasmMediaRecorder)<< Q_FUNC_INFO <<
"could not find MediaRecorder";
134 m_jsMediaRecorderDevice->resumeStream();
140 qCDebug(qWasmMediaRecorder)<< Q_FUNC_INFO <<
"could not find MediaRecorder";
143 m_jsMediaRecorderDevice->stopStream();
153 return m_session && m_session->camera();
159 emscripten::val navigator = emscripten::val::global(
"navigator");
160 emscripten::val mediaDevices = navigator[
"mediaDevices"];
162 if (mediaDevices.isNull() || mediaDevices.isUndefined()) {
163 qCDebug(qWasmMediaRecorder) <<
"MediaDevices are undefined or null";
169 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO << m_session;
171 emscripten::val stream = emscripten::val::undefined();
173 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"has camera";
177 m_jsMediaRecorderDevice->setNeedsCamera(
true);
179 if (m_video.isNull() || m_video.isUndefined()) {
180 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"video element not found";
184 stream = m_video[
"srcObject"];
185 if (stream.isNull() || stream.isUndefined()) {
186 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"Video input stream not found";
191 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"has audio";
192 stream =
static_cast<QWasmAudioInput *>(m_session->audioInput())->mediaStream();
194 if (stream.isNull() || stream.isUndefined()) {
195 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"Audio input stream not found";
198 m_jsMediaRecorderDevice->setNeedsAudio(m_session->hasAudio());
200 if (stream.isNull() || stream.isUndefined()) {
201 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"No input stream found";
204 if (!m_jsMediaRecorderDevice->open(QIODeviceBase::ReadOnly)) {
205 qWarning() <<
"m_jsMediaRecorderDevice is not open";
209 QObject::connect(m_jsMediaRecorderDevice.get(), &JsMediaRecorder::readyRead,
this, [
this]() {
211 if (m_jsMediaRecorderDevice->bytesAvailable()) {
213 QByteArray mediaData = m_jsMediaRecorderDevice->read(m_jsMediaRecorderDevice->bytesAvailable());
215 m_durationMs = m_durationTimer->elapsed();
216 if (m_outputTarget->isOpen())
217 m_outputTarget->write(mediaData.constData(), mediaData.length());
219 if (m_durationMs > 0) {
220 emit durationChanged(m_durationMs);
221 qCDebug(qWasmMediaRecorder) <<
"duration changed" << m_durationMs;
226 m_jsMediaRecorderDevice->setStream(stream);
236 m_hasMediaSettings =
true;
243 setTrackContraints(m_mediaSettings, stream);
249void QWasmMediaRecorder::setTrackContraints(QMediaEncoderSettings &settings, emscripten::val stream)
251 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO
252 << settings.audioSampleRate()
253 << settings.videoResolution();
255 if (stream.isUndefined() || stream.isNull()) {
256 qCDebug(qWasmMediaRecorder)<< Q_FUNC_INFO <<
"could not find MediaStream";
260 emscripten::val navigator = emscripten::val::global(
"navigator");
261 emscripten::val mediaDevices = navigator[
"mediaDevices"];
264 emscripten::val allConstraints = mediaDevices.call<emscripten::val>(
"getSupportedConstraints");
267 emscripten::val videoParams = emscripten::val::object();
268 emscripten::val constraints = emscripten::val::object();
269 videoParams.set(
"resizeMode",std::string(
"crop-and-scale"));
272 if (settings.videoFrameRate() > 0)
273 videoParams.set(
"frameRate", emscripten::val(settings.videoFrameRate()));
274 if (settings.videoResolution().height() > 0)
275 videoParams.set(
"height",
276 emscripten::val(settings.videoResolution().height()));
277 if (settings.videoResolution().width() > 0)
278 videoParams.set(
"width", emscripten::val(settings.videoResolution().width()));
280 constraints.set(
"video", videoParams);
283 emscripten::val audioParams = emscripten::val::object();
284 if (settings.audioSampleRate() > 0)
285 audioParams.set(
"sampleRate", emscripten::val(settings.audioSampleRate()));
286 if (settings.audioBitRate() > 0)
287 audioParams.set(
"sampleSize", emscripten::val(settings.audioBitRate()));
288 if (settings.audioChannelCount() > 0)
289 audioParams.set(
"channelCount", emscripten::val(settings.audioChannelCount()));
291 constraints.set(
"audio", audioParams);
293 if (hasCamera() && stream[
"active"].as<
bool>()) {
294 emscripten::val videoTracks = emscripten::val::undefined();
295 videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
296 if (videoTracks.isNull() || videoTracks.isUndefined()) {
297 qCDebug(qWasmMediaRecorder) <<
"no video tracks";
300 if (videoTracks[
"length"].as<
int>() > 0) {
302 qstdweb::Promise::make(videoTracks[0], QStringLiteral(
"applyConstraints"),
304 [
this](emscripten::val result) {
309 [
this](emscripten::val theError) {
310 qCDebug(qWasmMediaRecorder)
311 << theError[
"code"].as<
int>()
312 << QString::fromStdString(theError[
"message"].as<std::string>());
313 updateError(QMediaRecorder::ResourceError,
314 QString::fromStdString(theError[
"message"].as<std::string>()));
316 .finallyFunc = []() {},
327 m_jsMediaRecorderDevice->startStreaming();
332 QString m_targetFileName = outputLocation().toLocalFile();
334 QString suffix = m_mediaSettings.mimeType().preferredSuffix();
337 if (!m_mediaSettings.mimeType().isValid()) {
338 qWarning() <<
"mimetype not valid, using m4v";
339 suffix = QStringLiteral(
".m4v");
341 if (m_targetFileName.isEmpty()) {
342 m_targetFileName = QDir::homePath() + u"/tmp."_s + suffix;
343 QPlatformMediaRecorder::setOutputLocation(QUrl::fromLocalFile(m_targetFileName));
346 m_outputTarget =
new QFile(m_targetFileName,
this);
347 if (!m_outputTarget->open(QIODevice::WriteOnly)) {
348 qWarning() <<
"target file is not writable";
QWasmVideoOutput * cameraOutput()
Combined button and popup list for selecting options.
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")