5#include <qaudiodevice.h>
6#include <qcameradevice.h>
18using MediaInputStreamMap = QMap<std::string, MediaInputStreamEntry>;
27 if (mode.testFlag(QIODevice::WriteOnly))
29 return QIODevice::open(mode);
39 return m_buffer.size();
46 return QIODevice::seek(pos);
51 qint64 bytesToRead = qMin(maxSize, (qint64)m_buffer.size());
52 memcpy(data, m_buffer.constData(), bytesToRead);
53 m_buffer = m_buffer.right(m_buffer.size() - bytesToRead);
59 Q_UNREACHABLE_RETURN(0);
62void JsMediaRecorder::audioDataAvailable(emscripten::val aBlob,
double timeCodeDifference)
64 Q_UNUSED(timeCodeDifference)
65 if (aBlob.isUndefined() || aBlob.isNull()) {
66 qWarning() <<
"blob is null";
70 auto fileReader = std::make_shared<qstdweb::FileReader>();
72 fileReader->onError([=](emscripten::val theError) {
73 emit streamError(QMediaRecorder::ResourceError,
74 QString::fromStdString(theError[
"message"].as<std::string>()));
77 fileReader->onAbort([=](emscripten::val) {
78 emit streamError(QMediaRecorder::ResourceError, QStringLiteral(
"File read aborted"));
81 fileReader->onLoad([=](emscripten::val) {
82 if (fileReader->val().isNull() || fileReader->val().isUndefined())
84 qstdweb::ArrayBuffer result = fileReader->result();
85 if (result.val().isNull() || result.val().isUndefined())
88 m_buffer.append(qstdweb::Uint8Array(result).copyToQByteArray());
92 fileReader->readAsArrayBuffer(qstdweb::Blob(aBlob));
95void JsMediaRecorder::setTrackContraints(QMediaEncoderSettings &settings, emscripten::val stream)
97 if (stream.isUndefined() || stream.isNull()) {
98 qWarning()<<
"could not find MediaStream";
102 emscripten::val navigator = emscripten::val::global(
"navigator");
103 emscripten::val mediaDevices = navigator[
"mediaDevices"];
106 emscripten::val allConstraints = mediaDevices.call<emscripten::val>(
"getSupportedConstraints");
109 emscripten::val videoParams = emscripten::val::object();
110 emscripten::val constraints = emscripten::val::object();
111 videoParams.set(
"resizeMode",std::string(
"crop-and-scale"));
114 if (settings.videoFrameRate() > 0)
115 videoParams.set(
"frameRate", emscripten::val(settings.videoFrameRate()));
116 if (settings.videoResolution().height() > 0)
117 videoParams.set(
"height",
118 emscripten::val(settings.videoResolution().height()));
119 if (settings.videoResolution().width() > 0)
120 videoParams.set(
"width", emscripten::val(settings.videoResolution().width()));
122 constraints.set(
"video", videoParams);
125 emscripten::val audioParams = emscripten::val::object();
126 if (settings.audioSampleRate() > 0)
127 audioParams.set(
"sampleRate", emscripten::val(settings.audioSampleRate()));
128 if (settings.audioBitRate() > 0)
129 audioParams.set(
"sampleSize", emscripten::val(settings.audioBitRate()));
130 if (settings.audioChannelCount() > 0)
131 audioParams.set(
"channelCount", emscripten::val(settings.audioChannelCount()));
133 constraints.set(
"audio", audioParams);
135 if (m_needsCamera && stream[
"active"].as<
bool>()) {
136 emscripten::val videoTracks = emscripten::val::undefined();
137 videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
138 if (videoTracks.isNull() || videoTracks.isUndefined()) {
139 qWarning() <<
"no video tracks";
142 if (videoTracks[
"length"].as<
int>() > 0) {
144 qstdweb::Promise::make(videoTracks[0],
145 QStringLiteral(
"applyConstraints"), {
147 [
this]([[maybe_unused]] emscripten::val result) {
151 [
this](emscripten::val theError) {
153 << theError[
"code"].as<
int>()
154 << theError[
"message"].as<std::string>();
155 emit streamError(QMediaRecorder::ResourceError,
156 QString::fromStdString(theError[
"message"].as<std::string>()));
158 .finallyFunc = []() {},
167 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
168 qWarning() <<
"could not find MediaRecorder";
171 m_mediaRecorder.call<
void>(
"pause");
176 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
177 qWarning() <<
"could not find MediaRecorder";
181 m_mediaRecorder.call<
void>(
"resume");
186 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
187 qWarning()<<
"could not find MediaRecorder";
190 if (m_mediaRecorder[
"state"].as<std::string>() ==
"recording")
191 m_mediaRecorder.call<
void>(
"stop");
197 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
198 qWarning() <<
"could not find MediaStream";
202 constexpr int sliceSizeInMs = 256;
204 m_mediaRecorder.call<
void>(
"start", emscripten::val(sliceSizeInMs));
209 emscripten::val emMediaSettings = emscripten::val::object();
210 QMediaFormat::VideoCodec videoCodec = m_mediaSettings.videoCodec();
211 QMediaFormat::AudioCodec audioCodec = m_mediaSettings.audioCodec();
212 QMediaFormat::FileFormat fileFormat = m_mediaSettings.fileFormat();
216 if (!m_mediaSettings.mimeType().name().isEmpty()) {
217 mimeCodec = m_mediaSettings.mimeType().name();
219 if (videoCodec != QMediaFormat::VideoCodec::Unspecified)
220 mimeCodec += QStringLiteral(
": codecs=");
222 if (audioCodec != QMediaFormat::AudioCodec::Unspecified) {
226 if (fileFormat != QMediaFormat::UnspecifiedFormat)
227 mimeCodec += QMediaFormat::fileFormatName(m_mediaSettings.fileFormat());
229 emMediaSettings.set(
"mimeType", mimeCodec.toStdString());
232 if (m_mediaSettings.audioBitRate() > 0)
233 emMediaSettings.set(
"audioBitsPerSecond", emscripten::val(m_mediaSettings.audioBitRate()));
235 if (m_mediaSettings.videoBitRate() > 0)
236 emMediaSettings.set(
"videoBitsPerSecond", emscripten::val(m_mediaSettings.videoBitRate()));
239 m_mediaRecorder = emscripten::val::global(
"MediaRecorder").new_(stream, emMediaSettings);
241 if (m_mediaRecorder.isNull() || m_mediaRecorder.isUndefined()) {
242 qWarning() <<
"MediaRecorder could not be found";
245 m_mediaRecorder.set(
"data-mediarecordercontext",
246 emscripten::val(quintptr(
reinterpret_cast<
void *>(
this))));
248 if (!m_mediaStreamDataAvailable.isNull()) {
249 m_mediaStreamDataAvailable.reset();
250 m_mediaStreamStopped.reset();
251 m_mediaStreamError.reset();
252 m_mediaStreamStart.reset();
253 m_mediaStreamPause.reset();
254 m_mediaStreamResume.reset();
258 auto callback = [](emscripten::val blob) {
259 if (blob.isUndefined() || blob.isNull()) {
260 qWarning() <<
"blob is null";
263 if (blob[
"target"].isUndefined() || blob[
"target"].isNull())
265 if (blob[
"data"].isUndefined() || blob[
"data"].isNull())
267 if (blob[
"target"][
"data-mediarecordercontext"].isUndefined()
268 || blob[
"target"][
"data-mediarecordercontext"].isNull())
272 blob[
"target"][
"data-mediarecordercontext"].as<quintptr>());
275 const double timeCode =
276 blob.hasOwnProperty(
"timecode") ? blob[
"timecode"].as<
double>() : 0;
277 recorder->audioDataAvailable(blob[
"data"], timeCode);
281 m_mediaStreamDataAvailable.reset(
282 new qstdweb::EventCallback(m_mediaRecorder,
"dataavailable", callback));
285 auto stoppedCallback = [
this](emscripten::val event) {
286 if (event.isUndefined() || event.isNull()) {
287 qWarning() <<
"event is null";
290 m_currentState = QMediaRecorder::StoppedState;
292 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
293 emit recorder->stopped();
296 m_mediaStreamStopped.reset(
297 new qstdweb::EventCallback(m_mediaRecorder,
"stop", stoppedCallback));
300 auto errorCallback = [
this](emscripten::val theError) {
301 if (theError.isUndefined() || theError.isNull()) {
302 qWarning() <<
"error is null";
306 emit streamError(QMediaRecorder::ResourceError,
307 QString::fromStdString(theError[
"message"].as<std::string>()));
310 m_mediaStreamError.reset(
new qstdweb::EventCallback(m_mediaRecorder,
"error", errorCallback));
313 auto startCallback = [
this](emscripten::val event) {
314 if (event.isUndefined() || event.isNull()) {
315 qWarning() <<
"event is null";
320 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
321 m_currentState = QMediaRecorder::RecordingState;
322 emit recorder->started();
325 m_mediaStreamStart.reset(
new qstdweb::EventCallback(m_mediaRecorder,
"start", startCallback));
328 auto pauseCallback = [
this](emscripten::val event) {
329 if (event.isUndefined() || event.isNull()) {
330 qWarning() <<
"event is null";
335 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
336 m_currentState = QMediaRecorder::PausedState;
337 emit recorder->paused();
340 m_mediaStreamPause.reset(
new qstdweb::EventCallback(m_mediaRecorder,
"pause", pauseCallback));
343 auto resumeCallback = [
this](emscripten::val event) {
344 if (event.isUndefined() || event.isNull()) {
345 qWarning() <<
"event is null";
348 m_currentState = QMediaRecorder::RecordingState;
351 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
352 emit recorder->resumed();
355 m_mediaStreamResume.reset(
356 new qstdweb::EventCallback(m_mediaRecorder,
"resume", resumeCallback));
361 return m_buffer.size();
383 auto it = s_wasmMediaInputStreams()->find(deviceId);
384 if (it == s_wasmMediaInputStreams()->end())
386 if (--it->referenceCount <= 0) {
388 s_wasmMediaInputStreams()->erase(it);
394 if (--m_consumerCount <= 0) {
396 stopMediaStream(m_mediaStream);
402 for (
auto it = s_wasmMediaInputStreams()->begin(); it != s_wasmMediaInputStreams()->end(); ++it) {
404 if (stream && stream->m_needsAudio)
405 stream->setAudioStreamDevice(audioDeviceId);
411 m_videoResolution = resolution;
412 m_minFrameRate = minFrameRate;
413 m_maxFrameRate = maxFrameRate;
418 if (!m_mediaStream.isUndefined() && !m_mediaStream.isNull()) {
419 if (!m_mediaStream.isNull() && !m_mediaStream.isUndefined()
420 && !m_mediaStream[
"getTracks"].isUndefined() && m_mediaStream[
"active"].as<
bool>()) {
421 m_needsVideo =
false;
423 replaceMediaTrack(id);
430 qstdweb::PromiseCallbacks getUserMediaCallback{
433 [
this, id](emscripten::val newStream) {
435 std::string getTracksCommand;
437 getTracksCommand =
"getAudioTracks";
439 getTracksCommand =
"getVideoTracks";
441 emscripten::val currentTracks = m_mediaStream.call<emscripten::val>(getTracksCommand.c_str());
443 if (!currentTracks.isUndefined() && currentTracks[
"length"].as<
int>() > 0) {
444 emscripten::val currentTrackForType = currentTracks[0];
445 emscripten::val settings = currentTrackForType.call<emscripten::val>(
"getSettings");
447 if (!settings.isNull() && !settings.isUndefined()) {
448 if (settings[
"deviceId"].as<std::string>() != id) {
449 m_mediaStream.call<
void>(
"removeTrack", currentTrackForType);
450 currentTrackForType.call<
void>(
"stop");
452 emscripten::val newTracks = newStream.call<emscripten::val>(getTracksCommand.c_str());
454 m_mediaStream.call<
void>(
"addTrack", newTracks[0]);
455 newStream.call<
void>(
"removeTrack", newTracks[0]);
459 emit mediaAudioStreamReady();
461 emit mediaVideoStreamReady();
465 qWarning() <<
" we still need to add this track";
469 [](emscripten::val error) {
471 <<
"replaceTrack getUserMedia failed."
472 << error[
"name"].as<std::string>()
473 << error[
"message"].as<std::string>();
475 .finallyFunc =
nullptr
478 emscripten::val mediaDevices = emscripten::val::global(
"navigator")[
"mediaDevices"];
479 qstdweb::Promise::make(mediaDevices, QStringLiteral(
"getUserMedia"),
480 std::move(getUserMediaCallback), setDeviceConstraints(id));
485 std::string deviceIdString = id;
486 if (deviceIdString.find(
"System") != std::string::npos)
487 deviceIdString.clear();
489 emscripten::val constraints = emscripten::val::object();
491 emscripten::val audioConstraints = emscripten::val::object();
492 audioConstraints.set(
"audio", m_needsAudio);
493 if (!deviceIdString.empty()) {
494 emscripten::val exactDeviceId = emscripten::val::object();
495 exactDeviceId.set(
"exact", deviceIdString);
496 audioConstraints.set(
"deviceId", exactDeviceId);
498 constraints.set(
"audio", audioConstraints);
500 constraints.set(
"audio",
false);
504 emscripten::val videoContraints = emscripten::val::object();
505 if (!deviceIdString.empty()) {
506 emscripten::val exactDeviceId = emscripten::val::object();
507 exactDeviceId.set(
"exact", deviceIdString);
508 videoContraints.set(
"deviceId", exactDeviceId);
510 videoContraints.set(
"resizeMode", std::string(
"crop-and-scale"));
511 if (m_videoResolution.isValid()) {
512 videoContraints.set(
"width", emscripten::val(m_videoResolution.width()));
513 videoContraints.set(
"height", emscripten::val(m_videoResolution.height()));
515 if (m_minFrameRate > 0 || m_maxFrameRate > 0) {
516 emscripten::val frameRateConstraint = emscripten::val::object();
517 if (m_minFrameRate > 0)
518 frameRateConstraint.set(
"min", emscripten::val(m_minFrameRate));
519 if (m_maxFrameRate > 0)
520 frameRateConstraint.set(
"max", emscripten::val(m_maxFrameRate));
521 videoContraints.set(
"frameRate", frameRateConstraint);
523 constraints.set(
"video", videoContraints);
530 emscripten::val navigator = emscripten::val::global(
"navigator");
531 emscripten::val mediaDevices = navigator[
"mediaDevices"];
533 if (mediaDevices.isNull() || mediaDevices.isUndefined()) {
534 qWarning() <<
"No media devices found";
540 if (!m_mediaStream.isNull() && !m_mediaStream.isUndefined())
541 m_active = m_mediaStream[
"active"].as<
bool>();
549 QTimer::singleShot(0,
this, [
this]() {
551 emit mediaVideoStreamReady();
553 emit mediaAudioStreamReady();
558 qstdweb::PromiseCallbacks getUserMediaCallback{
561 [
this](emscripten::val stream) {
562 setupMediaStream(stream);
565 [](emscripten::val error) {
567 <<
"setStreamDevice getUserMedia fail"
568 << error[
"name"].as<std::string>()
569 << error[
"message"].as<std::string>();
571 .finallyFunc =
nullptr
575 qstdweb::Promise::make(mediaDevices, QStringLiteral(
"getUserMedia"),
576 std::move(getUserMediaCallback), setDeviceConstraints(id));
581 m_mediaStream = mStream;
582 m_active = mStream[
"active"].as<
bool>();
584 auto activeStreamCallback = [=](emscripten::val) {
586 emit activated(m_active);
588 m_activeStreamEvent.reset(
new qstdweb::EventCallback(m_mediaStream,
"active", activeStreamCallback));
590 auto inactiveStreamCallback = [=](emscripten::val) {
592 emit activated(m_active);
594 m_inactiveStreamEvent.reset(
new qstdweb::EventCallback(m_mediaStream,
"inactive", inactiveStreamCallback));
597 emit mediaAudioStreamReady();
599 emit mediaVideoStreamReady();
604 if (!mediaStream.isNull() && !mediaStream.isUndefined() && !mediaStream[
"getTracks"].isUndefined()) {
605 emscripten::val tracks = mediaStream.call<emscripten::val>(
"getTracks");
606 if (!tracks.isUndefined() && tracks[
"length"].as<
int>() > 0) {
607 for (
int i = 0; i < tracks[
"length"].as<
int>(); i++) {
608 tracks[i].call<
void>(
"stop");
612 mediaStream = emscripten::val::undefined();
Combined button and popup list for selecting options.
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex)
JsMediaInputStream * stream