5#include <qaudiodevice.h>
6#include <qcameradevice.h>
11Q_GLOBAL_STATIC(JsMediaInputStream, s_wasmMediaInputStreamInstance);
17 if (mode.testFlag(QIODevice::WriteOnly))
19 return QIODevice::open(mode);
29 return m_buffer.size();
36 return QIODevice::seek(pos);
41 qint64 bytesToRead = qMin(maxSize, (qint64)m_buffer.size());
42 memcpy(data, m_buffer.constData(), bytesToRead);
43 m_buffer = m_buffer.right(m_buffer.size() - bytesToRead);
49 Q_UNREACHABLE_RETURN(0);
52void JsMediaRecorder::audioDataAvailable(emscripten::val aBlob,
double timeCodeDifference)
54 Q_UNUSED(timeCodeDifference)
55 if (aBlob.isUndefined() || aBlob.isNull()) {
56 qWarning() <<
"blob is null";
60 auto fileReader = std::make_shared<qstdweb::FileReader>();
62 fileReader->onError([=](emscripten::val theError) {
63 emit streamError(QMediaRecorder::ResourceError,
64 QString::fromStdString(theError[
"message"].as<std::string>()));
67 fileReader->onAbort([=](emscripten::val) {
68 emit streamError(QMediaRecorder::ResourceError, QStringLiteral(
"File read aborted"));
71 fileReader->onLoad([=](emscripten::val) {
72 if (fileReader->val().isNull() || fileReader->val().isUndefined())
74 qstdweb::ArrayBuffer result = fileReader->result();
75 if (result.val().isNull() || result.val().isUndefined())
78 m_buffer.append(qstdweb::Uint8Array(result).copyToQByteArray());
82 fileReader->readAsArrayBuffer(qstdweb::Blob(aBlob));
85void JsMediaRecorder::setTrackContraints(QMediaEncoderSettings &settings, emscripten::val stream)
87 if (stream.isUndefined() || stream.isNull()) {
88 qWarning()<<
"could not find MediaStream";
92 emscripten::val navigator = emscripten::val::global(
"navigator");
93 emscripten::val mediaDevices = navigator[
"mediaDevices"];
96 emscripten::val allConstraints = mediaDevices.call<emscripten::val>(
"getSupportedConstraints");
99 emscripten::val videoParams = emscripten::val::object();
100 emscripten::val constraints = emscripten::val::object();
101 videoParams.set(
"resizeMode",std::string(
"crop-and-scale"));
104 if (settings.videoFrameRate() > 0)
105 videoParams.set(
"frameRate", emscripten::val(settings.videoFrameRate()));
106 if (settings.videoResolution().height() > 0)
107 videoParams.set(
"height",
108 emscripten::val(settings.videoResolution().height()));
109 if (settings.videoResolution().width() > 0)
110 videoParams.set(
"width", emscripten::val(settings.videoResolution().width()));
112 constraints.set(
"video", videoParams);
115 emscripten::val audioParams = emscripten::val::object();
116 if (settings.audioSampleRate() > 0)
117 audioParams.set(
"sampleRate", emscripten::val(settings.audioSampleRate()));
118 if (settings.audioBitRate() > 0)
119 audioParams.set(
"sampleSize", emscripten::val(settings.audioBitRate()));
120 if (settings.audioChannelCount() > 0)
121 audioParams.set(
"channelCount", emscripten::val(settings.audioChannelCount()));
123 constraints.set(
"audio", audioParams);
125 if (m_needsCamera && stream[
"active"].as<
bool>()) {
126 emscripten::val videoTracks = emscripten::val::undefined();
127 videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
128 if (videoTracks.isNull() || videoTracks.isUndefined()) {
129 qWarning() <<
"no video tracks";
132 if (videoTracks[
"length"].as<
int>() > 0) {
134 qstdweb::Promise::make(videoTracks[0],
135 QStringLiteral(
"applyConstraints"), {
137 [
this]([[maybe_unused]] emscripten::val result) {
141 [
this](emscripten::val theError) {
143 << theError[
"code"].as<
int>()
144 << theError[
"message"].as<std::string>();
145 emit streamError(QMediaRecorder::ResourceError,
146 QString::fromStdString(theError[
"message"].as<std::string>()));
148 .finallyFunc = []() {},
157 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
158 qWarning() <<
"could not find MediaRecorder";
161 m_mediaRecorder.call<
void>(
"pause");
166 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
167 qWarning() <<
"could not find MediaRecorder";
171 m_mediaRecorder.call<
void>(
"resume");
176 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
177 qWarning()<<
"could not find MediaRecorder";
180 if (m_mediaRecorder[
"state"].as<std::string>() ==
"recording")
181 m_mediaRecorder.call<
void>(
"stop");
187 if (m_mediaRecorder.isUndefined() || m_mediaRecorder.isNull()) {
188 qWarning() <<
"could not find MediaStream";
192 constexpr int sliceSizeInMs = 256;
194 m_mediaRecorder.call<
void>(
"start", emscripten::val(sliceSizeInMs));
199 emscripten::val emMediaSettings = emscripten::val::object();
200 QMediaFormat::VideoCodec videoCodec = m_mediaSettings.videoCodec();
201 QMediaFormat::AudioCodec audioCodec = m_mediaSettings.audioCodec();
202 QMediaFormat::FileFormat fileFormat = m_mediaSettings.fileFormat();
206 if (!m_mediaSettings.mimeType().name().isEmpty()) {
207 mimeCodec = m_mediaSettings.mimeType().name();
209 if (videoCodec != QMediaFormat::VideoCodec::Unspecified)
210 mimeCodec += QStringLiteral(
": codecs=");
212 if (audioCodec != QMediaFormat::AudioCodec::Unspecified) {
216 if (fileFormat != QMediaFormat::UnspecifiedFormat)
217 mimeCodec += QMediaFormat::fileFormatName(m_mediaSettings.fileFormat());
219 emMediaSettings.set(
"mimeType", mimeCodec.toStdString());
222 if (m_mediaSettings.audioBitRate() > 0)
223 emMediaSettings.set(
"audioBitsPerSecond", emscripten::val(m_mediaSettings.audioBitRate()));
225 if (m_mediaSettings.videoBitRate() > 0)
226 emMediaSettings.set(
"videoBitsPerSecond", emscripten::val(m_mediaSettings.videoBitRate()));
229 m_mediaRecorder = emscripten::val::global(
"MediaRecorder").new_(stream, emMediaSettings);
231 if (m_mediaRecorder.isNull() || m_mediaRecorder.isUndefined()) {
232 qWarning() <<
"MediaRecorder could not be found";
235 m_mediaRecorder.set(
"data-mediarecordercontext",
236 emscripten::val(quintptr(
reinterpret_cast<
void *>(
this))));
238 if (!m_mediaStreamDataAvailable.isNull()) {
239 m_mediaStreamDataAvailable.reset();
240 m_mediaStreamStopped.reset();
241 m_mediaStreamError.reset();
242 m_mediaStreamStart.reset();
243 m_mediaStreamPause.reset();
244 m_mediaStreamResume.reset();
248 auto callback = [](emscripten::val blob) {
249 if (blob.isUndefined() || blob.isNull()) {
250 qWarning() <<
"blob is null";
253 if (blob[
"target"].isUndefined() || blob[
"target"].isNull())
255 if (blob[
"data"].isUndefined() || blob[
"data"].isNull())
257 if (blob[
"target"][
"data-mediarecordercontext"].isUndefined()
258 || blob[
"target"][
"data-mediarecordercontext"].isNull())
262 blob[
"target"][
"data-mediarecordercontext"].as<quintptr>());
265 const double timeCode =
266 blob.hasOwnProperty(
"timecode") ? blob[
"timecode"].as<
double>() : 0;
267 recorder->audioDataAvailable(blob[
"data"], timeCode);
271 m_mediaStreamDataAvailable.reset(
272 new qstdweb::EventCallback(m_mediaRecorder,
"dataavailable", callback));
275 auto stoppedCallback = [
this](emscripten::val event) {
276 if (event.isUndefined() || event.isNull()) {
277 qWarning() <<
"event is null";
280 m_currentState = QMediaRecorder::StoppedState;
282 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
283 emit recorder->stopped();
286 m_mediaStreamStopped.reset(
287 new qstdweb::EventCallback(m_mediaRecorder,
"stop", stoppedCallback));
290 auto errorCallback = [
this](emscripten::val theError) {
291 if (theError.isUndefined() || theError.isNull()) {
292 qWarning() <<
"error is null";
296 emit streamError(QMediaRecorder::ResourceError,
297 QString::fromStdString(theError[
"message"].as<std::string>()));
300 m_mediaStreamError.reset(
new qstdweb::EventCallback(m_mediaRecorder,
"error", errorCallback));
303 auto startCallback = [
this](emscripten::val event) {
304 if (event.isUndefined() || event.isNull()) {
305 qWarning() <<
"event is null";
310 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
311 m_currentState = QMediaRecorder::RecordingState;
312 emit recorder->started();
315 m_mediaStreamStart.reset(
new qstdweb::EventCallback(m_mediaRecorder,
"start", startCallback));
318 auto pauseCallback = [
this](emscripten::val event) {
319 if (event.isUndefined() || event.isNull()) {
320 qWarning() <<
"event is null";
325 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
326 m_currentState = QMediaRecorder::PausedState;
327 emit recorder->paused();
330 m_mediaStreamPause.reset(
new qstdweb::EventCallback(m_mediaRecorder,
"pause", pauseCallback));
333 auto resumeCallback = [
this](emscripten::val event) {
334 if (event.isUndefined() || event.isNull()) {
335 qWarning() <<
"event is null";
338 m_currentState = QMediaRecorder::RecordingState;
341 event[
"target"][
"data-mediarecordercontext"].as<quintptr>());
342 emit recorder->resumed();
345 m_mediaStreamResume.reset(
346 new qstdweb::EventCallback(m_mediaRecorder,
"resume", resumeCallback));
351 return m_buffer.size();
364 return s_wasmMediaInputStreamInstance();
369 if (!m_mediaStream.isUndefined() && !m_mediaStream.isNull()) {
370 if (!m_mediaStream.isNull() && !m_mediaStream.isUndefined()
371 && !m_mediaStream[
"getTracks"].isUndefined() && m_mediaStream[
"active"].as<
bool>()) {
372 m_needsVideo =
false;
374 replaceMediaTrack(id);
381 qstdweb::PromiseCallbacks getUserMediaCallback{
384 [
this, id](emscripten::val newStream) {
386 std::string getTracksCommand;
388 getTracksCommand =
"getAudioTracks";
390 getTracksCommand =
"getVideoTracks";
392 emscripten::val currentTracks = m_mediaStream.call<emscripten::val>(getTracksCommand.c_str());
394 if (!currentTracks.isUndefined() && currentTracks[
"length"].as<
int>() > 0) {
395 emscripten::val currentTrackForType = currentTracks[0];
396 emscripten::val settings = currentTrackForType.call<emscripten::val>(
"getSettings");
398 if (!settings.isNull() && !settings.isUndefined()) {
399 if (settings[
"deviceId"].as<std::string>() != id) {
400 m_mediaStream.call<
void>(
"removeTrack", currentTrackForType);
401 currentTrackForType.call<
void>(
"stop");
403 emscripten::val newTracks = newStream.call<emscripten::val>(getTracksCommand.c_str());
405 m_mediaStream.call<
void>(
"addTrack", newTracks[0]);
409 emit mediaAudioStreamReady();
411 emit mediaVideoStreamReady();
415 qWarning() <<
" we still need to add this track";
419 [](emscripten::val error) {
421 <<
"replaceTrack getUserMedia failed."
422 << error[
"name"].as<std::string>()
423 << error[
"message"].as<std::string>();
427 emscripten::val mediaDevices = emscripten::val::global(
"navigator")[
"mediaDevices"];
428 qstdweb::Promise::make(mediaDevices, QStringLiteral(
"getUserMedia"),
429 std::move(getUserMediaCallback), setDeviceConstraints(id));
434 std::string deviceIdString = id;
435 if (deviceIdString.find(
"System") != std::string::npos)
436 deviceIdString.clear();
438 emscripten::val constraints = emscripten::val::object();
440 emscripten::val audioConstraints = emscripten::val::object();
441 audioConstraints.set(
"audio", m_needsAudio);
442 if (!deviceIdString.empty()) {
443 emscripten::val exactDeviceId = emscripten::val::object();
444 exactDeviceId.set(
"exact", deviceIdString);
445 audioConstraints.set(
"deviceId", exactDeviceId);
447 constraints.set(
"audio", audioConstraints);
449 constraints.set(
"audio",
false);
453 emscripten::val videoContraints = emscripten::val::object();
454 if (!deviceIdString.empty()) {
455 emscripten::val exactDeviceId = emscripten::val::object();
456 exactDeviceId.set(
"exact", deviceIdString);
457 videoContraints.set(
"deviceId", exactDeviceId);
459 videoContraints.set(
"resizeMode", std::string(
"crop-and-scale"));
460 constraints.set(
"video", videoContraints);
467 emscripten::val navigator = emscripten::val::global(
"navigator");
468 emscripten::val mediaDevices = navigator[
"mediaDevices"];
470 if (mediaDevices.isNull() || mediaDevices.isUndefined()) {
471 qWarning() <<
"No media devices found";
477 if (!m_mediaStream.isNull() && !m_mediaStream.isUndefined())
478 m_active = m_mediaStream[
"active"].as<
bool>();
481 replaceMediaTrack(id);
485 qstdweb::PromiseCallbacks getUserMediaCallback{
488 [
this](emscripten::val stream) {
489 setupMediaStream(stream);
492 [](emscripten::val error) {
494 <<
"setStreamDevice getUserMedia fail"
495 << error[
"name"].as<std::string>()
496 << error[
"message"].as<std::string>();
501 qstdweb::Promise::make(mediaDevices, QStringLiteral(
"getUserMedia"),
502 std::move(getUserMediaCallback), setDeviceConstraints(id));
507 m_mediaStream = mStream;
508 m_active = mStream[
"active"].as<
bool>();
510 auto activeStreamCallback = [=](emscripten::val) {
512 emit activated(m_active);
514 m_activeStreamEvent.reset(
new qstdweb::EventCallback(m_mediaStream,
"active", activeStreamCallback));
516 auto inactiveStreamCallback = [=](emscripten::val) {
518 emit activated(m_active);
520 m_inactiveStreamEvent.reset(
new qstdweb::EventCallback(m_mediaStream,
"inactive", inactiveStreamCallback));
522 emit mediaVideoStreamReady();
527 if (!mediaStream.isNull() && !mediaStream.isUndefined() && !mediaStream[
"getTracks"].isUndefined()) {
528 emscripten::val tracks = mediaStream.call<emscripten::val>(
"getTracks");
529 if (!tracks.isUndefined() && tracks[
"length"].as<
int>() > 0) {
530 for (
int i = 0; i < tracks[
"length"].as<
int>(); i++) {
531 tracks[i].call<
void>(
"stop");
535 mediaStream = emscripten::val::undefined();
Combined button and popup list for selecting options.