12#include <QMimeDatabase>
14#include <QGuiApplication>
15#include <QOpenGLContext>
17#include <QtGui/rhi/qrhi_platform.h>
18#include <qpa/qplatformwindow_p.h>
24#include <qvideosink.h>
25#include <private/qplatformvideosink_p.h>
26#include <private/qstdweb_p.h>
29#include <emscripten/bind.h>
30#include <emscripten/val.h>
35using namespace emscripten;
36using namespace Qt::Literals;
42 emscripten::val platformObject = emscripten::val::global(
"navigator")[
"platform"];
43 if (platformObject.call<
bool>(
"includes", emscripten::val(
"iPhone"))
44 || platformObject.call<
bool>(
"includes", emscripten::val(
"iPad")))
50 : QObject{ parent }, m_frameGrabber(
this)
52 if (isPlatformiOs()) {
56 emscripten_set_orientationchange_callback(
this,
false, &QWasmVideoOutput::orientationchangeCallback);
62 if (m_mediaInputStream) {
63 if (m_streamStarted) {
64 m_streamStarted =
false;
65 m_mediaInputStream->unregisterConsumer();
67 JsMediaInputStream::releaseInstance(m_cameraId);
73 if (m_pendingVideoSize == newSize)
76 m_pendingVideoSize = newSize;
77 updateVideoElementGeometry(QRect(0, 0, m_pendingVideoSize.width(), m_pendingVideoSize.height()));
82 m_currentVideoMode = mode;
96 emscripten::val promise = m_video.call<emscripten::val>(
"play");
99 if (promise.isUndefined() || promise.isNull() || promise[
"then"].isUndefined())
102 QPointer<QWasmVideoOutput> videoOutput(
this);
103 qstdweb::Promise::adoptPromise(promise, {
104 .catchFunc = [videoOutput](emscripten::val error) {
105 if (!videoOutput || videoOutput->m_isStopped)
110 const auto stringProperty = [&error](
const char *key) {
111 return error[key].isUndefined() || error[key].isNull()
113 : QString::fromStdString(error[key].as<std::string>());
115 const QString errorName = stringProperty(
"name");
116 const QString errorMessage = stringProperty(
"message");
119 const QString errorString =
120 "video.play(): "_L1 + (errorMessage.isEmpty() ? errorName : errorMessage);
126 if (errorName ==
"NotAllowedError"_L1) {
127 qCWarning(qWasmMediaVideoOutput) <<
"video.play() not allowed:" << errorMessage;
128 emit videoOutput->readyChanged(
false);
129 emit videoOutput->stateChanged(QWasmMediaPlayer::Stopped);
130 emit videoOutput->errorOccured(QMediaPlayer::AccessDeniedError, errorString);
137 if (!videoOutput->m_video[
"paused"].as<
bool>()) {
138 qCDebug(qWasmMediaVideoOutput) <<
"video.play() rejected with" << errorName
139 <<
"but playback resumed, ignoring";
143 qCWarning(qWasmMediaVideoOutput) <<
"video.play() rejected:" << errorName
145 emit videoOutput->readyChanged(
false);
146 emit videoOutput->stateChanged(QWasmMediaPlayer::Stopped);
147 emit videoOutput->errorOccured(QMediaPlayer::ResourceError, errorString);
154 if (m_video.isUndefined() || m_video.isNull()
157 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
161 switch (m_currentVideoMode) {
163 emscripten::val sourceObj = m_video[
"src"];
164 if ((sourceObj.isUndefined() || sourceObj.isNull()) && !m_source.isEmpty()) {
165 m_video.set(
"src", m_source);
168 m_video.call<
void>(
"load");
172 emit readyChanged(
true);
176 emscripten::val document = emscripten::val::global(
"document");
177 if (m_video[
"parentNode"].isNull() || m_video[
"parentNode"].isUndefined())
178 document[
"body"].call<
void>(
"appendChild", m_video);
180 if (!m_cameraIsReady) {
181 m_shouldBeStarted =
true;
185 m_connection = connect(m_mediaInputStream, &JsMediaInputStream::mediaVideoStreamReady,
this,
187 emscripten::val newStream = m_mediaInputStream->getMediaStream();
192 if (m_video[
"srcObject"].equals(newStream)) {
193 emit readyChanged(
true);
196 m_video.set(
"srcObject", newStream);
198 emscripten::val stream = m_video[
"srcObject"];
199 if (stream.isNull() || stream.isUndefined()) {
200 qCDebug(qWasmMediaVideoOutput) <<
"srcObject ERROR";
201 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
204 emscripten::val videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
205 if (videoTracks.isNull() || videoTracks.isUndefined()) {
206 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"videoTracks is null";
207 emit errorOccured(QMediaPlayer::ResourceError,
208 QStringLiteral(
"video surface error"));
211 if (videoTracks[
"length"].as<
int>() == 0) {
212 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"videoTracks count is 0";
213 emit errorOccured(QMediaPlayer::ResourceError,
214 QStringLiteral(
"video surface error"));
217 emscripten::val videoSettings = videoTracks[0].call<emscripten::val>(
"getSettings");
218 if (!videoSettings.isNull() && !videoSettings.isUndefined()) {
219 const int width = videoSettings[
"width"].as<
int>();
220 const int height = videoSettings[
"height"].as<
int>();
221 updateVideoElementGeometry(QRect(0, 0, width, height));
222 if (!videoSettings[
"frameRate"].isUndefined())
223 m_streamFrameRate = videoSettings[
"frameRate"].as<
double>();
229 emit readyChanged(
true);
232 m_mediaInputStream->setUseAudio(
false);
233 m_shouldBeStarted =
true;
234 m_mediaInputStream->setVideoConstraints(m_videoResolution, m_minFrameRate, m_maxFrameRate);
235 if (!m_streamStarted) {
236 m_streamStarted =
true;
237 m_mediaInputStream->registerConsumer();
239 m_mediaInputStream->setStreamDevice(m_cameraId);
256 qCWarning(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"mode=" << m_currentVideoMode;
257 if (m_video.isUndefined() || m_video.isNull()) {
258 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"Resource error"));
264 emscripten::val stream = m_video[
"srcObject"];
265 if (!stream.isNull() && !stream.isUndefined()) {
266 emscripten::val tracks = stream.call<emscripten::val>(
"getTracks");
267 const int count = tracks[
"length"].as<
int>();
268 for (
int i = 0; i < count; ++i)
269 tracks[i].call<
void>(
"stop");
271 }
else if (m_mediaInputStream && m_streamStarted) {
274 m_streamStarted =
false;
275 m_mediaInputStream->unregisterConsumer();
279 m_video.set(
"srcObject", emscripten::val::null());
280 disconnect(m_connection);
282 m_video.call<
void>(
"remove");
284 m_video.call<
void>(
"pause");
290 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO;
292 if (m_video.isUndefined() || m_video.isNull()) {
294 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
299 m_video.call<
void>(
"pause");
306 m_wasmSink->platformVideoSink()->setVideoFrame(QVideoFrame());
309 m_video.set(
"currentTime", emscripten::val(0));
310 m_video.call<
void>(
"load");
320 if (!surface || surface == m_wasmSink) {
324 m_wasmSink = surface;
329 if (m_video.isUndefined() || m_video.isNull()) {
334 return m_currentMediaStatus == MediaStatus::LoadedMedia;
339 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO << url;
341 m_source = url.toString();
343 if (m_video.isUndefined() || m_video.isNull()) {
351 if (url.isLocalFile()) {
352 QFile localFile(url.toLocalFile());
353 if (localFile.open(QIODevice::ReadOnly)) {
356 qWarning() <<
"Failed to open file";
361 updateVideoElementSource(m_source);
366 m_video.set(
"src", src.toStdString());
367 m_video.call<
void>(
"load");
372 m_cameraIsReady =
false;
373 if (m_mediaInputStream)
374 JsMediaInputStream::releaseInstance(m_cameraId);
375 m_mediaInputStream = JsMediaInputStream::instance(id);
377 m_mediaInputStream->setUseAudio(m_hasAudio);
378 m_mediaInputStream->setUseVideo(
true);
380 connect(m_mediaInputStream, &JsMediaInputStream::mediaVideoStreamReady,
this,
382 qCDebug(qWasmMediaVideoOutput) <<
"mediaVideoStreamReady" << m_shouldBeStarted;
384 m_cameraIsReady =
true;
385 if (m_shouldBeStarted) {
387 m_shouldBeStarted =
false;
396 m_videoResolution = resolution;
397 m_minFrameRate = minFrameRate;
398 m_maxFrameRate = maxFrameRate;
403 if (stream->bytesAvailable() == 0) {
404 qWarning() <<
"data not available";
405 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"data not available"));
408 if (m_video.isUndefined() || m_video.isNull()) {
409 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
414 QMimeType mime = db.mimeTypeForData(stream);
416 QByteArray buffer = stream->readAll();
418 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(buffer.data(), buffer.size(), mime.name().toStdString());
420 emscripten::val window = qstdweb::window();
422 if (window[
"safari"].isUndefined()) {
423 emscripten::val contentUrl = window[
"URL"].call<emscripten::val>(
"createObjectURL", contentBlob.val());
424 m_video.set(
"src", contentUrl);
425 m_source = QString::fromStdString(contentUrl.as<std::string>());
428 m_video.set(
"srcObject", contentBlob.val());
434 volume = qBound(qreal(0.0), volume, qreal(1.0));
435 m_video.set(
"volume", volume);
440 if (m_video.isUndefined() || m_video.isNull()) {
442 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
445 m_video.set(
"muted", muted);
450 return (!m_video.isUndefined() || !m_video.isNull())
451 ? (m_video[
"currentTime"].as<
double>() * 1000)
458 float positionToSetInSeconds =
float(positionMSecs) / 1000;
459 emscripten::val seekableTimeRange = m_video[
"seekable"];
460 if (!seekableTimeRange.isNull() || !seekableTimeRange.isUndefined()) {
462 if (seekableTimeRange[
"length"].as<
int>() < 1)
464 if (positionToSetInSeconds
465 >= seekableTimeRange.call<emscripten::val>(
"start", 0).as<
double>()
466 && positionToSetInSeconds
467 <= seekableTimeRange.call<emscripten::val>(
"end", 0).as<
double>()) {
468 m_requestedPosition = positionToSetInSeconds;
470 m_video.set(
"currentTime", m_requestedPosition);
474 qCDebug(qWasmMediaVideoOutput) <<
"m_requestedPosition" << m_requestedPosition;
479 if (m_video.isUndefined() || m_video.isNull()) {
481 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
485 emscripten::val seekableTimeRange = m_video[
"seekable"];
486 if (seekableTimeRange[
"length"].as<
int>() < 1)
488 if (!seekableTimeRange.isNull() || !seekableTimeRange.isUndefined()) {
489 bool isit = !QtPrivate::fuzzyCompare(
490 seekableTimeRange.call<emscripten::val>(
"start", 0).as<
double>(),
491 seekableTimeRange.call<emscripten::val>(
"end", 0).as<
double>());
499 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
this << id;
502 emscripten::val document = emscripten::val::global(
"document");
503 emscripten::val body = document[
"body"];
506 if (!m_video.isUndefined() && !m_video.isNull())
507 m_video.call<
void>(
"remove");
510 m_video = document.call<emscripten::val>(
"createElement", std::string(
"video"));
512 m_video.set(
"id", m_videoSurfaceId.c_str());
513 m_video.call<
void>(
"setAttribute", std::string(
"class"),
514 (m_currentVideoMode == QWasmVideoOutput::Camera ? std::string(
"Camera")
515 : std::string(
"Video")));
516 m_video.set(
"preload",
"metadata");
522 std::string originString =
"anonymous";
526 m_video.call<
void>(
"setAttribute", std::string(
"crossorigin"), originString);
527 body.call<
void>(
"appendChild", m_video);
530 document.call<emscripten::val>(
"createElement",
531 std::string(
"source")).set(
"src", m_source.toStdString());
536 emscripten::val style = m_video[
"style"];
537 style.set(
"position",
"absolute");
538 style.set(
"display",
"none");
540 if (!m_source.isEmpty())
541 updateVideoElementSource(m_source);
546 if (!m_video.isUndefined() && !m_video.isNull())
547 m_video.call<
void>(
"remove");
552 QRect videoElementRect(windowGeometry.topLeft(), windowGeometry.size());
554 emscripten::val style = m_video[
"style"];
555 style.set(
"left", QStringLiteral(
"%1px").arg(videoElementRect.left()).toStdString());
556 style.set(
"top", QStringLiteral(
"%1px").arg(videoElementRect.top()).toStdString());
557 m_video.set(
"width", videoElementRect.width());
558 m_video.set(
"height", videoElementRect.height());
559 style.set(
"z-index",
"999");
567 if (m_video.isUndefined() || m_video.isNull())
569 return m_video[
"duration"].as<
double>() * 1000;
574 m_video.set(
"playbackRate", emscripten::val(rate));
579 return (m_video.isUndefined() || m_video.isNull()) ? 0 : m_video[
"playbackRate"].as<
float>();
584 emscripten::val stream = m_video[
"srcObject"];
585 if ((!stream.isNull() && !stream.isUndefined()) && stream[
"active"].as<
bool>()) {
586 emscripten::val tracks = stream.call<emscripten::val>(
"getVideoTracks");
587 if (!tracks.isUndefined()) {
588 if (tracks[
"length"].as<
int>() == 0)
589 return emscripten::val::undefined();
591 emscripten::val track = tracks[0];
592 if (!track.isUndefined()) {
593 emscripten::val trackCaps = emscripten::val::undefined();
594 if (!track[
"getCapabilities"].isUndefined())
595 trackCaps = track.call<emscripten::val>(
"getCapabilities");
597 trackCaps = track.call<emscripten::val>(
"getSettings");
599 if (!trackCaps.isUndefined())
605 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"capabilities not available"));
608 return emscripten::val::undefined();
613 emscripten::val stream = m_video[
"srcObject"];
614 if (stream.isNull() || stream.isUndefined()
615 || stream[
"getVideoTracks"].isUndefined())
618 emscripten::val tracks = stream.call<emscripten::val>(
"getVideoTracks");
619 if (!tracks.isNull() || !tracks.isUndefined()) {
620 if (tracks[
"length"].as<
int>() == 0)
623 emscripten::val track = tracks[0];
624 emscripten::val contraint = emscripten::val::object();
625 contraint.set(
std::move(key), value);
626 track.call<emscripten::val>(
"applyConstraints", contraint);
635#include "moc_qwasmvideooutput_p.cpp"
void addCameraSourceElement(const std::string &id)
void updateVideoElementGeometry(const QRect &windowGeometry)
bool setDeviceSetting(const std::string &key, emscripten::val value)
emscripten::val surfaceElement()
emscripten::val getDeviceCapabilities()
void setVideoSize(const QSize &)
void setMuted(bool muted)
void setSource(const QUrl &url)
void setVideoMode(QWasmVideoOutput::WasmVideoMode mode)
void setVideoConstraints(QSize resolution, float minFrameRate, float maxFrameRate)
void seekTo(qint64 position)
void orientationChanged(int rotationIndex)
void setVolume(qreal volume)
void createVideoElement(const std::string &id)
void removeCurrentVideoElement()
void updateVideoElementSource(const QString &src)
std::string m_videoSurfaceId
void setSource(QIODevice *stream)
qint64 getCurrentPosition()
void setPlaybackRate(qreal rate)
Combined button and popup list for selecting options.
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")