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";
193 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"has audio";
194 stream =
static_cast<QWasmAudioInput *>(m_session->audioInput())->mediaStream();
196 if (stream.isNull() || stream.isUndefined()) {
197 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"Audio input stream not found";
200 m_jsMediaRecorderDevice->setNeedsAudio(m_session->hasAudio());
202 if (stream.isNull() || stream.isUndefined()) {
203 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO <<
"No input stream found";
206 if (!m_jsMediaRecorderDevice->open(QIODeviceBase::ReadOnly)) {
207 qWarning() <<
"m_jsMediaRecorderDevice is not open";
211 QObject::connect(m_jsMediaRecorderDevice.get(), &JsMediaRecorder::readyRead,
this, [
this]() {
213 if (m_jsMediaRecorderDevice->bytesAvailable()) {
215 QByteArray mediaData = m_jsMediaRecorderDevice->read(m_jsMediaRecorderDevice->bytesAvailable());
217 m_durationMs = m_durationTimer->elapsed();
218 if (m_outputTarget->isOpen())
219 m_outputTarget->write(mediaData, mediaData.length());
221 if (m_durationMs > 0) {
222 emit durationChanged(m_durationMs);
223 qCDebug(qWasmMediaRecorder) <<
"duration changed" << m_durationMs;
228 m_jsMediaRecorderDevice->setStream(stream);
238 m_hasMediaSettings =
true;
245 setTrackContraints(m_mediaSettings, stream);
251void QWasmMediaRecorder::setTrackContraints(QMediaEncoderSettings &settings, emscripten::val stream)
253 qCDebug(qWasmMediaRecorder) << Q_FUNC_INFO
254 << settings.audioSampleRate()
255 << settings.videoResolution();
257 if (stream.isUndefined() || stream.isNull()) {
258 qCDebug(qWasmMediaRecorder)<< Q_FUNC_INFO <<
"could not find MediaStream";
262 emscripten::val navigator = emscripten::val::global(
"navigator");
263 emscripten::val mediaDevices = navigator[
"mediaDevices"];
266 emscripten::val allConstraints = mediaDevices.call<emscripten::val>(
"getSupportedConstraints");
269 emscripten::val videoParams = emscripten::val::object();
270 emscripten::val constraints = emscripten::val::object();
271 videoParams.set(
"resizeMode",std::string(
"crop-and-scale"));
274 if (settings.videoFrameRate() > 0)
275 videoParams.set(
"frameRate", emscripten::val(settings.videoFrameRate()));
276 if (settings.videoResolution().height() > 0)
277 videoParams.set(
"height",
278 emscripten::val(settings.videoResolution().height()));
279 if (settings.videoResolution().width() > 0)
280 videoParams.set(
"width", emscripten::val(settings.videoResolution().width()));
282 constraints.set(
"video", videoParams);
285 emscripten::val audioParams = emscripten::val::object();
286 if (settings.audioSampleRate() > 0)
287 audioParams.set(
"sampleRate", emscripten::val(settings.audioSampleRate()));
288 if (settings.audioBitRate() > 0)
289 audioParams.set(
"sampleSize", emscripten::val(settings.audioBitRate()));
290 if (settings.audioChannelCount() > 0)
291 audioParams.set(
"channelCount", emscripten::val(settings.audioChannelCount()));
293 constraints.set(
"audio", audioParams);
295 if (hasCamera() && stream[
"active"].as<
bool>()) {
296 emscripten::val videoTracks = emscripten::val::undefined();
297 videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
298 if (videoTracks.isNull() || videoTracks.isUndefined()) {
299 qCDebug(qWasmMediaRecorder) <<
"no video tracks";
302 if (videoTracks[
"length"].as<
int>() > 0) {
304 qstdweb::Promise::make(videoTracks[0], QStringLiteral(
"applyConstraints"),
306 [
this](emscripten::val result) {
311 [
this](emscripten::val theError) {
312 qCDebug(qWasmMediaRecorder)
313 << theError[
"code"].as<
int>()
314 << QString::fromStdString(theError[
"message"].as<std::string>());
315 updateError(QMediaRecorder::ResourceError,
316 QString::fromStdString(theError[
"message"].as<std::string>()));
318 .finallyFunc = []() {},
329 m_jsMediaRecorderDevice->startStreaming();
334 QString m_targetFileName = outputLocation().toLocalFile();
336 QString suffix = m_mediaSettings.mimeType().preferredSuffix();
339 if (!m_mediaSettings.mimeType().isValid()) {
340 qWarning() <<
"mimetype not valid, using m4v";
341 suffix = QStringLiteral(
".m4v");
343 if (m_targetFileName.isEmpty()) {
344 m_targetFileName = QDir::homePath() + u"/tmp."_s + suffix;
345 QPlatformMediaRecorder::setOutputLocation(QUrl::fromLocalFile(m_targetFileName));
348 m_outputTarget =
new QFile(m_targetFileName,
this);
349 if (!m_outputTarget->open(QIODevice::WriteOnly)) {
350 qWarning() <<
"target file is not writable";
QWasmVideoOutput * cameraOutput()
Combined button and popup list for selecting options.
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")