5#include <qaudiodevice.h>
6#include <qcameradevice.h>
17using MediaInputStreamMap = QMap<std::string, MediaInputStreamEntry>;
26 if (mode.testFlag(QIODevice::WriteOnly))
28 return QIODevice::open(mode);
38 return m_buffer.size();
45 return QIODevice::seek(pos);
50 qint64 bytesToRead = qMin(maxSize, (qint64)m_buffer.size());
51 memcpy(data, m_buffer.constData(), bytesToRead);
52 m_buffer = m_buffer.right(m_buffer.size() - bytesToRead);
58 Q_UNREACHABLE_RETURN(0);
61void JsMediaRecorder::audioDataAvailable(emscripten::val aBlob,
double timeCodeDifference)
63 Q_UNUSED(timeCodeDifference)
64 if (aBlob.isUndefined() || aBlob.isNull()) {
65 qWarning() <<
"blob is null";
69 auto fileReader = std::make_shared<qstdweb::FileReader>();
71 fileReader->onError([=](emscripten::val theError) {
72 emit streamError(QMediaRecorder::ResourceError,
73 QString::fromStdString(theError[
"message"].as<std::string>()));
76 fileReader->onAbort([=](emscripten::val) {
77 emit streamError(QMediaRecorder::ResourceError, QStringLiteral(
"File read aborted"));
80 fileReader->onLoad([=](emscripten::val) {
81 if (fileReader->val().isNull() || fileReader->val().isUndefined())
83 qstdweb::ArrayBuffer result = fileReader->result();
84 if (result.val().isNull() || result.val().isUndefined())
87 m_buffer.append(qstdweb::Uint8Array(result).copyToQByteArray());
91 fileReader->readAsArrayBuffer(qstdweb::Blob(aBlob));
94void JsMediaRecorder::setTrackContraints(QMediaEncoderSettings &settings, emscripten::val stream)
96 if (stream.isUndefined() || stream.isNull()) {
97 qWarning()<<
"could not find MediaStream";
101 emscripten::val navigator = emscripten::val::global(
"navigator");
102 emscripten::val mediaDevices = navigator[
"mediaDevices"];
105 emscripten::val allConstraints = mediaDevices.call<emscripten::val>(
"getSupportedConstraints");
108 emscripten::val videoParams = emscripten::val::object();
109 emscripten::val constraints = emscripten::val::object();
110 videoParams.set(
"resizeMode",std::string(
"crop-and-scale"));
113 if (settings.videoFrameRate() > 0)
114 videoParams.set(
"frameRate", emscripten::val(settings.videoFrameRate()));
115 if (settings.videoResolution().height() > 0)
116 videoParams.set(
"height",
117 emscripten::val(settings.videoResolution().height()));
118 if (settings.videoResolution().width() > 0)
119 videoParams.set(
"width", emscripten::val(settings.videoResolution().width()));
121 constraints.set(
"video", videoParams);
124 emscripten::val audioParams = emscripten::val::object();
125 if (settings.audioSampleRate() > 0)
126 audioParams.set(
"sampleRate", emscripten::val(settings.audioSampleRate()));
127 if (settings.audioBitRate() > 0)
128 audioParams.set(
"sampleSize", emscripten::val(settings.audioBitRate()));
129 if (settings.audioChannelCount() > 0)
130 audioParams.set(
"channelCount", emscripten::val(settings.audioChannelCount()));
132 constraints.set(
"audio", audioParams);
134 if (m_needsCamera && stream[
"active"].as<
bool>()) {
135 emscripten::val videoTracks = emscripten::val::undefined();
136 videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
137 if (videoTracks.isNull() || videoTracks.isUndefined()) {
138 qWarning() <<
"no video tracks";
141 if (videoTracks[
"length"].as<
int>() > 0) {
143 qstdweb::Promise::make(videoTracks[0],
144 QStringLiteral(
"applyConstraints"), {
146 [
this]([[maybe_unused]] emscripten::val result) {
150 [
this](emscripten::val theError) {
152 << theError[
"code"].as<
int>()
153 << theError[
"message"].as<std::string>();
154 emit streamError(QMediaRecorder::ResourceError,
155 QString::fromStdString(theError[
"message"].as<std::string>()));
157 .finallyFunc = []() {},
166 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
167 qWarning() <<
"could not find MediaRecorder";
170 m_mediaRecorder.call<
void>(
"pause");
175 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
176 qWarning() <<
"could not find MediaRecorder";
180 m_mediaRecorder.call<
void>(
"resume");
185 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
186 qWarning()<<
"could not find MediaRecorder";
189 if (m_mediaRecorder[
"state"].as<std::string>() ==
"recording")
190 m_mediaRecorder.call<
void>(
"stop");
196 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
197 qWarning() <<
"could not find MediaStream";
201 constexpr int sliceSizeInMs = 256;
203 m_mediaRecorder.call<
void>(
"start", emscripten::val(sliceSizeInMs));
208 emscripten::val emMediaSettings = emscripten::val::object();
209 QMediaFormat::VideoCodec videoCodec = m_mediaSettings.videoCodec();
210 QMediaFormat::AudioCodec audioCodec = m_mediaSettings.audioCodec();
211 QMediaFormat::FileFormat fileFormat = m_mediaSettings.fileFormat();
215 if (!m_mediaSettings.mimeType().name().isEmpty()) {
216 mimeCodec = m_mediaSettings.mimeType().name();
218 if (videoCodec != QMediaFormat::VideoCodec::Unspecified)
219 mimeCodec += QStringLiteral(
": codecs=");
221 if (audioCodec != QMediaFormat::AudioCodec::Unspecified) {
225 if (fileFormat != QMediaFormat::UnspecifiedFormat)
226 mimeCodec += QMediaFormat::fileFormatName(m_mediaSettings.fileFormat());
228 emMediaSettings.set(
"mimeType", mimeCodec.toStdString());
231 if (m_mediaSettings.audioBitRate() > 0)
232 emMediaSettings.set(
"audioBitsPerSecond", emscripten::val(m_mediaSettings.audioBitRate()));
234 if (m_mediaSettings.videoBitRate() > 0)
235 emMediaSettings.set(
"videoBitsPerSecond", emscripten::val(m_mediaSettings.videoBitRate()));
238 m_mediaRecorder = emscripten::val::global(
"MediaRecorder").new_(stream, emMediaSettings);
240 if (m_mediaRecorder.isNull() || m_mediaRecorder.isUndefined()) {
241 qWarning() <<
"MediaRecorder could not be found";
244 m_mediaRecorder.set(
"data-mediarecordercontext",
245 emscripten::val(quintptr(
reinterpret_cast<
void *>(
this))));
247 if (!m_mediaStreamDataAvailable.isNull()) {
248 m_mediaStreamDataAvailable.reset();
249 m_mediaStreamStopped.reset();
250 m_mediaStreamError.reset();
251 m_mediaStreamStart.reset();
252 m_mediaStreamPause.reset();
253 m_mediaStreamResume.reset();
257 auto callback = [](emscripten::val blob) {
258 if (blob.isUndefined() || blob.isNull()) {
259 qWarning() <<
"blob is null";
262 if (blob[
"target"].isUndefined() || blob[
"target"].isNull())
264 if (blob[
"data"].isUndefined() || blob[
"data"].isNull())
266 if (blob[
"target"][
"data-mediarecordercontext"].isUndefined()
267 || blob[
"target"][
"data-mediarecordercontext"].isNull())
271 blob[
"target"][
"data-mediarecordercontext"].as<quintptr>());
274 const double timeCode =
275 blob.hasOwnProperty(
"timecode") ? blob[
"timecode"].as<
double>() : 0;
276 recorder->audioDataAvailable(blob[
"data"], timeCode);
280 m_mediaStreamDataAvailable.reset(
281 new qstdweb::EventCallback(m_mediaRecorder,
"dataavailable", callback));
284 auto stoppedCallback = [
this](emscripten::val event) {
285 if (event.isUndefined() || event.isNull()) {
286 qWarning() <<
"event is null";
289 m_currentState = QMediaRecorder::StoppedState;
291 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
292 emit recorder->stopped();
295 m_mediaStreamStopped.reset(
296 new qstdweb::EventCallback(m_mediaRecorder,
"stop", stoppedCallback));
299 auto errorCallback = [
this](emscripten::val theError) {
300 if (theError.isUndefined() || theError.isNull()) {
301 qWarning() <<
"error is null";
305 emit streamError(QMediaRecorder::ResourceError,
306 QString::fromStdString(theError[
"message"].as<std::string>()));
309 m_mediaStreamError.reset(
new qstdweb::EventCallback(m_mediaRecorder,
"error", errorCallback));
312 auto startCallback = [
this](emscripten::val event) {
313 if (event.isUndefined() || event.isNull()) {
314 qWarning() <<
"event is null";
319 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
320 m_currentState = QMediaRecorder::RecordingState;
321 emit recorder->started();
324 m_mediaStreamStart.reset(
new qstdweb::EventCallback(m_mediaRecorder,
"start", startCallback));
327 auto pauseCallback = [
this](emscripten::val event) {
328 if (event.isUndefined() || event.isNull()) {
329 qWarning() <<
"event is null";
334 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
335 m_currentState = QMediaRecorder::PausedState;
336 emit recorder->paused();
339 m_mediaStreamPause.reset(
new qstdweb::EventCallback(m_mediaRecorder,
"pause", pauseCallback));
342 auto resumeCallback = [
this](emscripten::val event) {
343 if (event.isUndefined() || event.isNull()) {
344 qWarning() <<
"event is null";
347 m_currentState = QMediaRecorder::RecordingState;
350 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
351 emit recorder->resumed();
354 m_mediaStreamResume.reset(
355 new qstdweb::EventCallback(m_mediaRecorder,
"resume", resumeCallback));
360 return m_buffer.size();
376 entry
.stream->m_deviceId = deviceId;
384 auto it = s_wasmMediaInputStreams()->find(deviceId);
385 if (it == s_wasmMediaInputStreams()->end())
387 if (--it->referenceCount <= 0) {
389 s_wasmMediaInputStreams()->erase(it);
395 for (
auto it = s_wasmMediaInputStreams()->begin(); it != s_wasmMediaInputStreams()->end(); ++it) {
397 if (stream && stream->m_needsAudio)
398 stream->setAudioStreamDevice(audioDeviceId);
404 m_videoResolution = resolution;
405 m_minFrameRate = minFrameRate;
406 m_maxFrameRate = maxFrameRate;
411 if (!m_mediaStream.isUndefined() && !m_mediaStream.isNull()) {
412 if (!m_mediaStream.isNull() && !m_mediaStream.isUndefined()
413 && !m_mediaStream[
"getTracks"].isUndefined() && m_mediaStream[
"active"].as<
bool>()) {
414 m_needsVideo =
false;
416 replaceMediaTrack(id);
423 qstdweb::PromiseCallbacks getUserMediaCallback{
426 [
this, id](emscripten::val newStream) {
428 std::string getTracksCommand;
430 getTracksCommand =
"getAudioTracks";
432 getTracksCommand =
"getVideoTracks";
434 emscripten::val currentTracks = m_mediaStream.call<emscripten::val>(getTracksCommand.c_str());
436 if (!currentTracks.isUndefined() && currentTracks[
"length"].as<
int>() > 0) {
437 emscripten::val currentTrackForType = currentTracks[0];
438 emscripten::val settings = currentTrackForType.call<emscripten::val>(
"getSettings");
440 if (!settings.isNull() && !settings.isUndefined()) {
441 if (settings[
"deviceId"].as<std::string>() != id) {
442 m_mediaStream.call<
void>(
"removeTrack", currentTrackForType);
443 currentTrackForType.call<
void>(
"stop");
445 emscripten::val newTracks = newStream.call<emscripten::val>(getTracksCommand.c_str());
447 m_mediaStream.call<
void>(
"addTrack", newTracks[0]);
448 newStream.call<
void>(
"removeTrack", newTracks[0]);
452 emit mediaAudioStreamReady();
454 emit mediaVideoStreamReady();
458 qWarning() <<
" we still need to add this track";
462 [](emscripten::val error) {
464 <<
"replaceTrack getUserMedia failed."
465 << error[
"name"].as<std::string>()
466 << error[
"message"].as<std::string>();
470 emscripten::val mediaDevices = emscripten::val::global(
"navigator")[
"mediaDevices"];
471 qstdweb::Promise::make(mediaDevices, QStringLiteral(
"getUserMedia"),
472 std::move(getUserMediaCallback), setDeviceConstraints(id));
477 std::string deviceIdString = id;
478 if (deviceIdString.find(
"System") != std::string::npos)
479 deviceIdString.clear();
481 emscripten::val constraints = emscripten::val::object();
483 emscripten::val audioConstraints = emscripten::val::object();
484 audioConstraints.set(
"audio", m_needsAudio);
485 if (!deviceIdString.empty()) {
486 emscripten::val exactDeviceId = emscripten::val::object();
487 exactDeviceId.set(
"exact", deviceIdString);
488 audioConstraints.set(
"deviceId", exactDeviceId);
490 constraints.set(
"audio", audioConstraints);
492 constraints.set(
"audio",
false);
496 emscripten::val videoContraints = emscripten::val::object();
497 if (!deviceIdString.empty()) {
498 emscripten::val exactDeviceId = emscripten::val::object();
499 exactDeviceId.set(
"exact", deviceIdString);
500 videoContraints.set(
"deviceId", exactDeviceId);
502 videoContraints.set(
"resizeMode", std::string(
"crop-and-scale"));
503 if (m_videoResolution.isValid()) {
504 videoContraints.set(
"width", emscripten::val(m_videoResolution.width()));
505 videoContraints.set(
"height", emscripten::val(m_videoResolution.height()));
507 if (m_minFrameRate > 0 || m_maxFrameRate > 0) {
508 emscripten::val frameRateConstraint = emscripten::val::object();
509 if (m_minFrameRate > 0)
510 frameRateConstraint.set(
"min", emscripten::val(m_minFrameRate));
511 if (m_maxFrameRate > 0)
512 frameRateConstraint.set(
"max", emscripten::val(m_maxFrameRate));
513 videoContraints.set(
"frameRate", frameRateConstraint);
515 constraints.set(
"video", videoContraints);
522 emscripten::val navigator = emscripten::val::global(
"navigator");
523 emscripten::val mediaDevices = navigator[
"mediaDevices"];
525 if (mediaDevices.isNull() || mediaDevices.isUndefined()) {
526 qWarning() <<
"No media devices found";
532 if (!m_mediaStream.isNull() && !m_mediaStream.isUndefined())
533 m_active = m_mediaStream[
"active"].as<
bool>();
536 replaceMediaTrack(id);
540 qstdweb::PromiseCallbacks getUserMediaCallback{
543 [
this](emscripten::val stream) {
544 setupMediaStream(stream);
547 [](emscripten::val error) {
549 <<
"setStreamDevice getUserMedia fail"
550 << error[
"name"].as<std::string>()
551 << error[
"message"].as<std::string>();
556 qstdweb::Promise::make(mediaDevices, QStringLiteral(
"getUserMedia"),
557 std::move(getUserMediaCallback), setDeviceConstraints(id));
562 m_mediaStream = mStream;
563 m_active = mStream[
"active"].as<
bool>();
565 auto activeStreamCallback = [=](emscripten::val) {
567 emit activated(m_active);
569 m_activeStreamEvent.reset(
new qstdweb::EventCallback(m_mediaStream,
"active", activeStreamCallback));
571 auto inactiveStreamCallback = [=](emscripten::val) {
573 emit activated(m_active);
575 m_inactiveStreamEvent.reset(
new qstdweb::EventCallback(m_mediaStream,
"inactive", inactiveStreamCallback));
578 emit mediaAudioStreamReady();
580 emit mediaVideoStreamReady();
585 if (!mediaStream.isNull() && !mediaStream.isUndefined() && !mediaStream[
"getTracks"].isUndefined()) {
586 emscripten::val tracks = mediaStream.call<emscripten::val>(
"getTracks");
587 if (!tracks.isUndefined() && tracks[
"length"].as<
int>() > 0) {
588 for (
int i = 0; i < tracks[
"length"].as<
int>(); i++) {
589 tracks[i].call<
void>(
"stop");
593 mediaStream = emscripten::val::undefined();
Combined button and popup list for selecting options.
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex)
JsMediaInputStream * stream