143 if (m_video.isUndefined() || m_video.isNull()
146 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
149 switch (m_currentVideoMode) {
151 emscripten::val sourceObj = m_video[
"src"];
152 if ((sourceObj.isUndefined() || sourceObj.isNull()) && !m_source.isEmpty()) {
153 m_video.set(
"src", m_source);
156 m_video.call<
void>(
"load");
159 m_video.call<
void>(
"play");
160 emit readyChanged(
true);
165 if (!m_cameraIsReady) {
166 m_shouldBeStarted =
true;
170 m_connection = connect(m_mediaInputStream, &JsMediaInputStream::mediaVideoStreamReady,
this,
172 m_video.set(
"srcObject", m_mediaInputStream->getMediaStream());
174 emscripten::val stream = m_video[
"srcObject"];
175 if (stream.isNull() || stream.isUndefined()) {
176 qCDebug(qWasmMediaVideoOutput) <<
"srcObject ERROR";
177 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
180 emscripten::val videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
181 if (videoTracks.isNull() || videoTracks.isUndefined()) {
182 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"videoTracks is null";
183 emit errorOccured(QMediaPlayer::ResourceError,
184 QStringLiteral(
"video surface error"));
187 if (videoTracks[
"length"].as<
int>() == 0) {
188 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"videoTracks count is 0";
189 emit errorOccured(QMediaPlayer::ResourceError,
190 QStringLiteral(
"video surface error"));
193 emscripten::val videoSettings = videoTracks[0].call<emscripten::val>(
"getSettings");
194 if (!videoSettings.isNull() && !videoSettings.isUndefined()) {
195 const int width = videoSettings[
"width"].as<
int>();
196 const int height = videoSettings[
"height"].as<
int>();
197 updateVideoElementGeometry(QRect(0, 0, width, height));
201 m_video.call<
void>(
"play");
203 emit readyChanged(
true);
205 videoFrameTimerCallback();
208 m_mediaInputStream->setUseAudio(
false);
209 m_shouldBeStarted =
true;
210 m_mediaInputStream->setStreamDevice(m_cameraId);
216 m_toBePaused =
false;
220 m_video.call<
void>(
"play");
553 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO;
557 auto timeUpdateCallback = [=](emscripten::val event) {
558 qCDebug(qWasmMediaVideoOutput) <<
"timeupdate";
561 emit progressChanged(event[
"target"][
"currentTime"].as<
double>() * 1000);
563 m_timeUpdateEvent.reset(
new QWasmEventHandler(m_video,
"timeupdate", timeUpdateCallback));
566 auto playCallback = [=](emscripten::val event) {
568 qCDebug(qWasmMediaVideoOutput) <<
"play" << m_video[
"src"].as<std::string>();
572 m_playEvent.reset(
new QWasmEventHandler(m_video,
"play", playCallback));
575 auto endedCallback = [=](emscripten::val event) {
577 qCDebug(qWasmMediaVideoOutput) <<
"ended";
578 m_currentMediaStatus = MediaStatus::EndOfMedia;
579 emit statusChanged(m_currentMediaStatus);
581 m_endedEvent.reset(
new QWasmEventHandler(m_video,
"ended", endedCallback));
584 auto durationChangeCallback = [=](emscripten::val event) {
585 qCDebug(qWasmMediaVideoOutput) <<
"durationChange";
588 qint64 dur = event[
"target"][
"duration"].as<
double>() * 1000;
589 emit durationChanged(dur);
591 m_durationChangeEvent.reset(
592 new QWasmEventHandler(m_video,
"durationchange", durationChangeCallback));
595 auto loadedDataCallback = [=](emscripten::val event) {
597 qCDebug(qWasmMediaVideoOutput) <<
"loaded data";
602 emit seekableChanged(m_isSeekable);
605 m_loadedDataEvent.reset(
new QWasmEventHandler(m_video,
"loadeddata", loadedDataCallback));
608 auto errorCallback = [=](emscripten::val event) {
609 qCDebug(qWasmMediaVideoOutput) <<
"error";
610 if (event.isUndefined() || event.isNull())
612 emit errorOccured(m_video[
"error"][
"code"].as<
int>(),
613 QString::fromStdString(m_video[
"error"][
"message"].as<std::string>()));
615 m_errorChangeEvent.reset(
new QWasmEventHandler(m_video,
"error", errorCallback));
618 auto resizeCallback = [=](emscripten::val event) {
620 qCDebug(qWasmMediaVideoOutput) <<
"resize";
622 updateVideoElementGeometry(
623 QRect(0, 0, m_video[
"videoWidth"].as<
int>(), m_video[
"videoHeight"].as<
int>()));
624 emit sizeChange(m_video[
"videoWidth"].as<
int>(), m_video[
"videoHeight"].as<
int>());
627 m_resizeChangeEvent.reset(
new QWasmEventHandler(m_video,
"resize", resizeCallback));
630 auto loadedMetadataCallback = [=](emscripten::val event) {
632 qCDebug(qWasmMediaVideoOutput) <<
"loaded meta data";
634 emit metaDataLoaded();
636 m_loadedMetadataChangeEvent.reset(
637 new QWasmEventHandler(m_video,
"loadedmetadata", loadedMetadataCallback));
640 auto loadStartCallback = [=](emscripten::val event) {
642 qCDebug(qWasmMediaVideoOutput) <<
"load started";
643 m_currentMediaStatus = MediaStatus::LoadingMedia;
644 emit statusChanged(m_currentMediaStatus);
647 m_loadStartChangeEvent.reset(
new QWasmEventHandler(m_video,
"loadstart", loadStartCallback));
651 auto canPlayCallback = [=](emscripten::val event) {
652 if (event.isUndefined() || event.isNull())
654 qCDebug(qWasmMediaVideoOutput) <<
"can play"
655 <<
"m_requestedPosition" << m_requestedPosition;
658 emit readyChanged(
true);
660 m_canPlayChangeEvent.reset(
new QWasmEventHandler(m_video,
"canplay", canPlayCallback));
663 auto canPlayThroughCallback = [=](emscripten::val event) {
665 qCDebug(qWasmMediaVideoOutput) <<
"can play through"
666 <<
"m_isStopped" << m_isStopped;
668 if (m_currentMediaStatus == MediaStatus::EndOfMedia)
671 if (m_isSeekable != seekable) {
672 m_isSeekable = seekable;
673 emit seekableChanged(m_isSeekable);
675 if (!m_isSeeking && !m_isStopped) {
676 emscripten::val timeRanges = m_video[
"buffered"];
677 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
678 && timeRanges[
"length"].as<
int>() == 1) {
679 double buffered = m_video[
"buffered"].call<emscripten::val>(
"end", 0).as<
double>();
680 const double duration = m_video[
"duration"].as<
double>();
682 if (duration == buffered) {
683 m_currentBufferedValue = 100;
684 emit bufferingChanged(m_currentBufferedValue);
687 constexpr int hasEnoughData = 4;
688 if (m_video[
"readyState"].as<
int>() == hasEnoughData) {
689 m_currentMediaStatus = MediaStatus::LoadedMedia;
690 emit statusChanged(m_currentMediaStatus);
697 m_canPlayThroughChangeEvent.reset(
698 new QWasmEventHandler(m_video,
"canplaythrough", canPlayThroughCallback));
701 auto seekingCallback = [=](emscripten::val event) {
703 qCDebug(qWasmMediaVideoOutput)
704 <<
"seeking started" << (m_video[
"currentTime"].as<
double>() * 1000);
707 m_seekingChangeEvent.reset(
new QWasmEventHandler(m_video,
"seeking", seekingCallback));
710 auto seekedCallback = [=](emscripten::val event) {
712 qCDebug(qWasmMediaVideoOutput) <<
"seeked" << (m_video[
"currentTime"].as<
double>() * 1000);
713 emit progressChanged(m_video[
"currentTime"].as<
double>() * 1000);
716 m_seekedChangeEvent.reset(
new QWasmEventHandler(m_video,
"seeked", seekedCallback));
719 auto emptiedCallback = [=](emscripten::val event) {
721 qCDebug(qWasmMediaVideoOutput) <<
"emptied";
722 emit readyChanged(
false);
723 m_currentMediaStatus = MediaStatus::EndOfMedia;
724 emit statusChanged(m_currentMediaStatus);
726 m_emptiedChangeEvent.reset(
new QWasmEventHandler(m_video,
"emptied", emptiedCallback));
729 auto stalledCallback = [=](emscripten::val event) {
731 qCDebug(qWasmMediaVideoOutput) <<
"stalled";
732 m_currentMediaStatus = MediaStatus::StalledMedia;
733 emit statusChanged(m_currentMediaStatus);
735 m_stalledChangeEvent.reset(
new QWasmEventHandler(m_video,
"stalled", stalledCallback));
738 auto waitingCallback = [=](emscripten::val event) {
741 qCDebug(qWasmMediaVideoOutput) <<
"waiting";
744 m_waitingChangeEvent.reset(
new QWasmEventHandler(m_video,
"waiting", waitingCallback));
749 auto playingCallback = [=](emscripten::val event) {
751 qCDebug(qWasmMediaVideoOutput) <<
"playing";
755 if (m_toBePaused || !m_isStopped) {
756 m_toBePaused =
false;
757 QMetaObject::invokeMethod(
this, &QWasmVideoOutput::videoFrameTimerCallback, Qt::QueuedConnection);
760 m_playingChangeEvent.reset(
new QWasmEventHandler(m_video,
"playing", playingCallback));
763 auto progesssCallback = [=](emscripten::val event) {
764 if (event.isUndefined() || event.isNull())
767 const double duration = event[
"target"][
"duration"].as<
double>();
771 emscripten::val timeRanges = event[
"target"][
"buffered"];
773 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
774 && timeRanges[
"length"].as<
int>() == 1) {
775 emscripten::val dVal = timeRanges.call<emscripten::val>(
"end", 0);
776 if (!dVal.isNull() || !dVal.isUndefined()) {
777 double bufferedEnd = dVal.as<
double>();
779 if (duration > 0 && bufferedEnd > 0) {
780 const double bufferedValue = (bufferedEnd / duration * 100);
781 qCDebug(qWasmMediaVideoOutput) <<
"progress buffered";
782 m_currentBufferedValue = bufferedValue;
783 emit bufferingChanged(m_currentBufferedValue);
784 if (bufferedEnd == duration)
785 m_currentMediaStatus = MediaStatus::BufferedMedia;
787 m_currentMediaStatus = MediaStatus::BufferingMedia;
788 emit statusChanged(m_currentMediaStatus);
793 m_progressChangeEvent.reset(
new QWasmEventHandler(m_video,
"progress", progesssCallback));
796 auto pauseCallback = [=](emscripten::val event) {
798 qCDebug(qWasmMediaVideoOutput) <<
"pause";
800 const double currentTime = m_video[
"currentTime"].as<
double>();
801 const double duration = m_video[
"duration"].as<
double>();
802 if ((currentTime > 0 && currentTime < duration) && (!m_isStopped && m_toBePaused)) {
806 m_video.set(
"currentTime", emscripten::val(0));
810 m_pauseChangeEvent.reset(
new QWasmEventHandler(m_video,
"pause", pauseCallback));
815 emscripten::val window = emscripten::val::global(
"window");
817 auto beforeUnloadCallback = [=](emscripten::val event) {
821 m_video.call<
void>(
"removeAttribute", emscripten::val(
"src"));
822 m_video.call<
void>(
"load");
824 m_beforeUnloadEvent.reset(
new QWasmEventHandler(window,
"beforeunload", beforeUnloadCallback));
947 emscripten::val videoElement = videoOutput->currentVideoElement();
953 emscripten::val oneVideoFrame = emscripten::val::take_ownership(
956 return Emval.toHandle(
new VideoFrame(Emval.toValue($0)));
958 return Emval.toHandle(null);
960 }, videoElement.as_handle()));
962 if (oneVideoFrame.isNull() || oneVideoFrame.isUndefined()) {
963 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"VideoFrame not ready yet, skipping";
967 emscripten::val options = emscripten::val::object();
968 emscripten::val rectOptions = emscripten::val::object();
970 int displayWidth = oneVideoFrame[
"displayWidth"].as<
int>();
971 int displayHeight = oneVideoFrame[
"displayHeight"].as<
int>();
973 rectOptions.set(
"width", displayWidth);
974 rectOptions.set(
"height", displayHeight);
975 options.set(
"rect", rectOptions);
977 options.set(
"format", std::string(
"RGBA"));
979 emscripten::val frameBytesAllocationSize = oneVideoFrame.call<emscripten::val>(
"allocationSize", options);
980 emscripten::val frameBuffer =
981 emscripten::val::global(
"Uint8Array").new_(frameBytesAllocationSize);
983 reinterpret_cast<QWasmVideoOutput*>(videoElement[
"data-qvideocontext"].as<quintptr>());
985 qstdweb::PromiseCallbacks copyToCallback;
986 copyToCallback.thenFunc = [
this, wasmVideoOutput, oneVideoFrame, frameBuffer,
987 displayWidth, displayHeight]
988 (emscripten::val frameLayout)
990 if (frameLayout.isNull() || frameLayout.isUndefined()) {
991 qCDebug(qWasmMediaVideoOutput) <<
"theres no frameLayout";
996 const QSize frameSize(displayWidth,
999 QByteArray frameBytes = QByteArray::fromEcmaUint8Array(frameBuffer);
1001 constexpr auto pixelFormat = QVideoFrameFormat::Format_RGBA8888;
1002 QVideoFrameFormat frameFormat = QVideoFrameFormat(frameSize, pixelFormat);
1004 if (m_useCameraRotation)
1005 frameFormat.setRotation(wasmVideoOutput->m_rotateBy);
1006 auto buffer = std::make_unique<QMemoryVideoBuffer>(
1007 std::move(frameBytes),
1008 frameLayout[0][
"stride"].as<
int>());
1010 QVideoFrame vFrame =
1011 QVideoFramePrivate::createFrame(std::move(buffer), std::move(frameFormat));
1013 if (!wasmVideoOutput) {
1014 qCDebug(qWasmMediaVideoOutput) <<
"ERROR:"
1015 <<
"data-qvideocontext not found";
1018 if (!wasmVideoOutput->m_wasmSink) {
1019 qWarning() <<
"ERROR ALERT!! video sink not set";
1022 wasmVideoOutput->m_wasmSink->setVideoFrame(vFrame);
1023 oneVideoFrame.call<emscripten::val>(
"close");
1025 copyToCallback.catchFunc = [&, wasmVideoOutput, oneVideoFrame](emscripten::val error)
1027 qCDebug(qWasmMediaVideoOutput) <<
"Error"
1028 << QString::fromStdString(error[
"name"].as<std::string>())
1029 << QString::fromStdString(error[
"message"].as<std::string>()) ;
1031 oneVideoFrame.call<emscripten::val>(
"close");
1036 qstdweb::Promise::make(oneVideoFrame, u"copyTo"_s, std::move(copyToCallback), frameBuffer, options);
1088 if (!wasmVideoOutput || !wasmVideoOutput
->isReady())
1091 emscripten_webgl_make_context_current(wasmVideoOutput->m_glContextHandle);
1093 GLuint rawTexId = 0;
1094 glGenTextures(1, &rawTexId);
1095 QGlTextureHandle texHandle{ rawTexId };
1097 glBindTexture(GL_TEXTURE_2D, texHandle.get());
1100 em_texImage2DFromVideo(wasmVideoOutput->m_glCanvas.as_handle(),
1101 wasmVideoOutput->m_videoSurfaceId.c_str(),
1104 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1107 glBindTexture(GL_TEXTURE_2D, 0);
1109 if (!texHandle || w == 0 || h == 0) {
1110 qCWarning(qWasmMediaVideoOutput) <<
"VideoFrame upload failed";
1114 std::unique_ptr<QHwVideoBuffer> hwBuffer =
1115 std::make_unique<QWasmGLTextureVideoBuffer>(std::move(texHandle), QSize(w, h));
1117 QVideoFrame vFrame =
1118 QVideoFramePrivate::createFrame(std::move(hwBuffer),
1119 QVideoFrameFormat(QSize(w, h),
1120 QVideoFrameFormat::Format_RGBA8888));
1122 wasmVideoOutput->m_wasmSink->setVideoFrame(vFrame);
EM_JS(void, qt_st_sink_loadWorkletModule,(EM_VAL ctxHandle, int callbackId, int channels), { var ctx=Emval.toValue(ctxHandle);var code=[ 'class QtSink extends AudioWorkletProcessor {', ' constructor(opts) {', ' super(opts);', ' this._numChannels=opts.processorOptions.channels|0;', ' this._queue=[];', ' this._pos=0;', ' this.port.onmessage=(e)=> { this._queue.push(e.data);};', ' }', ' process(inputs, outputs) {', ' var out=outputs[0];', ' if(!out||!out.length) return true;', ' var samplesPerChannel=out[0].length;', ' for(var i=0;i< samplesPerChannel;i++) {', ' while(this._queue.length > 0 &&this._pos >=this._queue[0].samplesPerChannel) {', ' this._queue.shift();', ' this._pos=0;', ' }', ' if(this._queue.length===0) break;', ' var frame=this._queue[0];', ' for(var channel=0;channel< out.length &&channel< frame.numChannels;channel++)', ' out[channel][i]=frame.data[channel *frame.samplesPerChannel+this._pos];', ' this._pos++;', ' }', ' this.port.postMessage(null);', ' return true;', ' }', '}', 'registerProcessor("qt-audio-sink", QtSink);'].join('\n');var blob=new Blob([code], { type:'application/javascript' });var url=URL.createObjectURL(blob);ctx.audioWorklet.addModule(url).then(function() { URL.revokeObjectURL(url);Module._qt_sinkWorkletReady(callbackId);});})