6#include <QtMultimedia/qaudiodevice.h>
7#include <QtMultimedia/qaudiooutput.h>
8#include <QtMultimedia/qmediadevices.h>
9#include <QtCore/qfile.h>
10#include <QtCore/qfileinfo.h>
11#include <QtCore/qloggingcategory.h>
12#include <QtCore/qmimedatabase.h>
13#include <QtCore/qurl.h>
17using namespace Qt::Literals;
21QWasmAudioOutput::QWasmAudioOutput(QAudioOutput *parent)
22 : QPlatformAudioOutput(parent)
26QWasmAudioOutput::~QWasmAudioOutput() =
default;
28void QWasmAudioOutput::setAudioDevice(
const QAudioDevice &audioDevice)
30 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
34void QWasmAudioOutput::setVideoElement(emscripten::val videoElement)
36 m_videoElement = videoElement;
39emscripten::val QWasmAudioOutput::videoElement()
41 return m_videoElement;
44void QWasmAudioOutput::setMuted(
bool muted)
46 emscripten::val realElement = videoElement();
47 if (!realElement.isUndefined()) {
48 realElement.set(
"muted", muted);
51 if (m_audio.isUndefined() || m_audio.isNull()) {
52 qCDebug(qWasmMediaAudioOutput) <<
"Error"
53 <<
"Audio element could not be created";
54 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
55 QStringLiteral(
"Media file could not be opened"));
58 m_audio.set(
"mute", muted);
61void QWasmAudioOutput::setVolume(
float volume)
63 volume = qBound(qreal(0.0), volume, qreal(1.0));
64 emscripten::val realElement = videoElement();
65 if (!realElement.isUndefined()) {
66 realElement.set(
"volume", volume);
69 if (m_audio.isUndefined() || m_audio.isNull()) {
70 qCDebug(qWasmMediaAudioOutput) <<
"Error"
71 <<
"Audio element not available";
72 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
73 QStringLiteral(
"Media file could not be opened"));
77 m_audio.set(
"volume", volume);
80void QWasmAudioOutput::setSource(
const QUrl &url)
82 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << url;
88 createAudioElement(device.id().toStdString());
90 if (m_audio.isUndefined() || m_audio.isNull()) {
91 qCDebug(qWasmMediaAudioOutput) <<
"Error"
92 <<
"Audio element could not be created";
93 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
94 QStringLiteral(
"Audio element could not be created"));
98 emscripten::val document = emscripten::val::global(
"document");
99 emscripten::val body = document[
"body"];
101 m_audio.set(
"id", device.id().toStdString());
103 body.call<
void>(
"appendChild", m_audio);
106 if (url.isLocalFile()) {
107 qCDebug(qWasmMediaAudioOutput) <<
"is localfile";
108 m_source = url.toLocalFile();
110 QFile mediaFile(m_source);
111 if (!mediaFile.exists()) {
112 qCDebug(qWasmMediaAudioOutput) <<
"Error"
113 <<
"Media file does not exist";
114 QMetaObject::invokeMethod(
this, &QWasmAudioOutput::errorOccured, Qt::QueuedConnection,
115 QMediaPlayer::ResourceError,
116 QStringLiteral(
"Media file does not exist"));
121 if (!mediaFile.open(QIODevice::ReadOnly)) {
122 qCDebug(qWasmMediaAudioOutput) <<
"Error"
123 <<
"Media file could not be opened";
124 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
125 QStringLiteral(
"Media file could not be opened"));
130 QByteArray content = mediaFile.readAll();
133 qCDebug(qWasmMediaAudioOutput) << db.mimeTypeForData(content).name();
135 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content.constData(), content.size());
136 emscripten::val contentUrl =
137 qstdweb::window()[
"URL"].call<emscripten::val>(
"createObjectURL", contentBlob.val());
139 emscripten::val audioSourceElement =
140 document.call<emscripten::val>(
"createElement", std::string(
"source"));
142 audioSourceElement.set(
"src", contentUrl);
145 QFileInfo info(m_source);
146 QMimeType mimeType = db.mimeTypeForFile(info);
148 audioSourceElement.set(
"type", mimeType.name().toStdString());
149 m_audio.call<
void>(
"appendChild", audioSourceElement);
151 m_audio.call<
void>(
"setAttribute", emscripten::val(
"srcObject"), contentUrl);
154 m_source = url.toString();
155 m_audio.set(
"src", m_source.toStdString());
157 m_audio.set(
"id", device.id().toStdString());
159 body.call<
void>(
"appendChild", m_audio);
160 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
162 doElementCallbacks();
165void QWasmAudioOutput::setSource(QIODevice *stream)
167 m_audioIODevice = stream;
170void QWasmAudioOutput::start()
172 if (m_audio.isNull() || m_audio.isUndefined()) {
173 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
174 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
175 QStringLiteral(
"Audio element resource error"));
179 m_audio.call<
void>(
"play");
182void QWasmAudioOutput::stop()
184 if (m_audio.isNull() || m_audio.isUndefined()) {
185 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
186 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
187 QStringLiteral(
"Audio element resource error"));
190 if (!m_source.isEmpty()) {
192 m_audio.set(
"currentTime", emscripten::val(0));
194 if (m_audioIODevice) {
195 m_audioIODevice->close();
196 delete m_audioIODevice;
201void QWasmAudioOutput::pause()
203 if (m_audio.isNull() || m_audio.isUndefined()) {
204 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
205 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
206 QStringLiteral(
"Audio element resource error"));
209 m_audio.call<emscripten::val>(
"pause");
212void QWasmAudioOutput::createAudioElement(
const std::string &id)
214 emscripten::val document = emscripten::val::global(
"document");
215 m_audio = document.call<emscripten::val>(
"createElement", std::string(
"audio"));
217 if (id ==
"System output") {
218 emscripten::val audioContext = emscripten::val::global(
"window")[
"AudioContext"].new_();
219 emscripten::val sourceNode = audioContext.call<emscripten::val>(
"createMediaElementSource", m_audio);
221 sourceNode.call<
void>(
"connect", audioContext[
"destination"]);
232 if (!m_audio.hasOwnProperty(
"sinkId") || m_audio[
"sinkId"].isUndefined()) {
236 std::string usableId = id;
237 if (usableId.empty() || usableId ==
"System output")
238 usableId = QMediaDevices::defaultAudioOutput().id();
240 qstdweb::PromiseCallbacks sinkIdCallbacks{
241 .thenFunc = [](emscripten::val) { qCWarning(qWasmMediaAudioOutput) <<
"setSinkId ok"; },
243 [](emscripten::val) {
244 qCWarning(qWasmMediaAudioOutput) <<
"Error while trying to setSinkId";
246 .finallyFunc =
nullptr
248 qstdweb::Promise::make(m_audio, u"setSinkId"_s, std::move(sinkIdCallbacks), std::move(usableId));
250 m_audio.set(
"id", usableId.c_str());
253void QWasmAudioOutput::doElementCallbacks()
256 auto errorCallback = [&](emscripten::val event) {
257 qCDebug(qWasmMediaAudioOutput) <<
"error";
258 if (event.isUndefined() || event.isNull())
260 Q_EMIT errorOccured(m_audio[
"error"][
"code"].as<
int>(),
261 QString::fromStdString(m_audio[
"error"][
"message"].as<std::string>()));
263 QString errorMessage =
264 QString::fromStdString(m_audio[
"error"][
"message"].as<std::string>());
265 if (errorMessage.isEmpty()) {
266 switch (m_audio[
"error"][
"code"].as<
int>()) {
267 case AudioElementError::MEDIA_ERR_ABORTED:
268 errorMessage = QStringLiteral(
"aborted by the user agent at the user's request.");
270 case AudioElementError::MEDIA_ERR_NETWORK:
271 errorMessage = QStringLiteral(
"network error.");
273 case AudioElementError::MEDIA_ERR_DECODE:
274 errorMessage = QStringLiteral(
"decoding error.");
276 case AudioElementError::MEDIA_ERR_SRC_NOT_SUPPORTED:
277 errorMessage = QStringLiteral(
"src attribute not suitable.");
281 qCDebug(qWasmMediaAudioOutput) << m_audio[
"error"][
"code"].as<
int>() << errorMessage;
283 Q_EMIT errorOccured(m_audio[
"error"][
"code"].as<
int>(), errorMessage);
285 m_errorChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"error", errorCallback));
288 auto loadedDataCallback = [&](emscripten::val event) {
290 qCDebug(qWasmMediaAudioOutput) <<
"loaded data";
291 qstdweb::window()[
"URL"].call<emscripten::val>(
"revokeObjectURL", m_audio[
"src"]);
293 m_loadedDataEvent.reset(
new qstdweb::EventCallback(m_audio,
"loadeddata", loadedDataCallback));
296 auto canPlayCallback = [&](emscripten::val event) {
297 if (event.isUndefined() || event.isNull())
299 qCDebug(qWasmMediaAudioOutput) <<
"can play";
300 Q_EMIT readyChanged(
true);
301 Q_EMIT stateChanged(QWasmMediaPlayer::Preparing);
303 m_canPlayChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"canplay", canPlayCallback));
306 auto canPlayThroughCallback = [&](emscripten::val event) {
308 Q_EMIT stateChanged(QWasmMediaPlayer::Prepared);
310 m_canPlayThroughChangeEvent.reset(
311 new qstdweb::EventCallback(m_audio,
"canplaythrough", canPlayThroughCallback));
314 auto playCallback = [&](emscripten::val event) {
316 qCDebug(qWasmMediaAudioOutput) <<
"play";
317 Q_EMIT stateChanged(QWasmMediaPlayer::Started);
319 m_playEvent.reset(
new qstdweb::EventCallback(m_audio,
"play", playCallback));
322 auto durationChangeCallback = [&](emscripten::val event) {
323 qCDebug(qWasmMediaAudioOutput) <<
"durationChange";
326 Q_EMIT durationChanged(event[
"target"][
"duration"].as<
double>() * 1000);
328 m_durationChangeEvent.reset(
329 new qstdweb::EventCallback(m_audio,
"durationchange", durationChangeCallback));
332 auto endedCallback = [&](emscripten::val event) {
334 qCDebug(qWasmMediaAudioOutput) <<
"ended";
335 m_currentMediaStatus = QMediaPlayer::EndOfMedia;
336 Q_EMIT statusChanged(m_currentMediaStatus);
338 m_endedEvent.reset(
new qstdweb::EventCallback(m_audio,
"ended", endedCallback));
341 auto progesssCallback = [&](emscripten::val event) {
342 if (event.isUndefined() || event.isNull())
344 qCDebug(qWasmMediaAudioOutput) <<
"progress";
345 float duration = event[
"target"][
"duration"].as<
int>();
349 emscripten::val timeRanges = event[
"target"][
"buffered"];
351 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
352 && timeRanges[
"length"].as<
int>() == 1) {
353 emscripten::val dVal = timeRanges.call<emscripten::val>(
"end", 0);
355 if (!dVal.isNull() || !dVal.isUndefined()) {
356 double bufferedEnd = dVal.as<
double>();
358 if (duration > 0 && bufferedEnd > 0) {
359 float bufferedValue = (bufferedEnd / duration * 100);
360 qCDebug(qWasmMediaAudioOutput) <<
"progress buffered" << bufferedValue;
362 Q_EMIT bufferingChanged(m_currentBufferedValue);
363 if (bufferedEnd == duration)
364 m_currentMediaStatus = QMediaPlayer::BufferedMedia;
366 m_currentMediaStatus = QMediaPlayer::BufferingMedia;
368 Q_EMIT statusChanged(m_currentMediaStatus);
373 m_progressChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"progress", progesssCallback));
376 auto timeUpdateCallback = [&](emscripten::val event) {
377 qCDebug(qWasmMediaAudioOutput)
378 <<
"timeupdate" << (event[
"target"][
"currentTime"].as<
double>() * 1000);
381 Q_EMIT progressChanged(event[
"target"][
"currentTime"].as<
double>() * 1000);
383 m_timeUpdateEvent.reset(
new qstdweb::EventCallback(m_audio,
"timeupdate", timeUpdateCallback));
386 auto pauseCallback = [&](emscripten::val event) {
388 qCDebug(qWasmMediaAudioOutput) <<
"pause";
390 int currentTime = m_audio[
"currentTime"].as<
int>();
391 int duration = m_audio[
"duration"].as<
int>();
392 if ((currentTime > 0 && currentTime < duration)) {
393 Q_EMIT stateChanged(QWasmMediaPlayer::Paused);
395 Q_EMIT stateChanged(QWasmMediaPlayer::Stopped);
398 m_pauseChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"pause", pauseCallback));
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)