Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qwasmaudiooutput.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR
3// GPL-2.0-only OR GPL-3.0-only
4
5#include <qaudiodevice.h>
6#include <qaudiooutput.h>
8
9#include <QMimeDatabase>
10#include <QtCore/qloggingcategory.h>
11#include <QMediaDevices>
12#include <QUrl>
13#include <QFile>
14#include <QMimeDatabase>
15#include <QFileInfo>
16
18
19static Q_LOGGING_CATEGORY(qWasmMediaAudioOutput, "qt.multimedia.wasm.audiooutput")
20
23{
24}
25
27
29{
30 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
31 device = audioDevice;
32}
33
35{
36 if (m_audio.isUndefined() || m_audio.isNull()) {
37 qCDebug(qWasmMediaAudioOutput) << "Error"
38 << "Audio element could not be created";
40 QStringLiteral("Media file could not be opened"));
41 return;
42 }
43 m_audio.set("mute", muted);
44}
45
47{
48 if (m_audio.isUndefined() || m_audio.isNull()) {
49 qCDebug(qWasmMediaAudioOutput) << "Error"
50 << "Audio element could not be created";
52 QStringLiteral("Media file could not be opened"));
53 return;
54 }
55 volume = qBound(qreal(0.0), volume, qreal(1.0));
56 m_audio.set("volume", volume);
57}
58
60{
61 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << url;
62 if (url.isEmpty()) {
63 stop();
64 return;
65 }
66
67 createAudioElement(device.id().toStdString());
68
69 if (m_audio.isUndefined() || m_audio.isNull()) {
70 qCDebug(qWasmMediaAudioOutput) << "Error"
71 << "Audio element could not be created";
73 QStringLiteral("Audio element could not be created"));
74 return;
75 }
76
77 emscripten::val document = emscripten::val::global("document");
78 emscripten::val body = document["body"];
79
80 m_audio.set("id", device.id().toStdString());
81
82 body.call<void>("appendChild", m_audio);
83
84
85 if (url.isLocalFile()) { // is localfile
86 qCDebug(qWasmMediaAudioOutput) << "is localfile";
87 m_source = url.toLocalFile();
88
89 QFile mediaFile(m_source);
90 if (!mediaFile.open(QIODevice::ReadOnly)) {
91 qCDebug(qWasmMediaAudioOutput) << "Error"
92 << "Media file could not be opened";
94 QStringLiteral("Media file could not be opened"));
95 return;
96 }
97
98 // local files are relatively small due to browser filesystem being restricted
99 QByteArray content = mediaFile.readAll();
100
102 qCDebug(qWasmMediaAudioOutput) << db.mimeTypeForData(content).name();
103
104 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content.constData(), content.size());
105 emscripten::val contentUrl =
106 qstdweb::window()["URL"].call<emscripten::val>("createObjectURL", contentBlob.val());
107
108 emscripten::val audioSourceElement =
109 document.call<emscripten::val>("createElement", std::string("source"));
110
111 audioSourceElement.set("src", contentUrl);
112
113 // work around Safari not being able to read audio from blob URLs.
114 QFileInfo info(m_source);
116
117 audioSourceElement.set("type", mimeType.name().toStdString());
118 m_audio.call<void>("appendChild", audioSourceElement);
119
120 m_audio.call<void>("setAttribute", emscripten::val("srcObject"), contentUrl);
121
122 } else {
123 m_source = url.toString();
124 m_audio.set("src", m_source.toStdString());
125 }
126 m_audio.set("id", device.id().toStdString());
127
128 body.call<void>("appendChild", m_audio);
129 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
130
131 doElementCallbacks();
132}
133
135{
136 m_audioIODevice = stream;
137}
138
140{
141 if (m_audio.isNull() || m_audio.isUndefined()) {
142 qCDebug(qWasmMediaAudioOutput) << "audio failed to start";
144 QStringLiteral("Audio element resource error"));
145 return;
146 }
147
148 m_audio.call<void>("play");
149}
150
152{
153 if (m_audio.isNull() || m_audio.isUndefined()) {
154 qCDebug(qWasmMediaAudioOutput) << "audio failed to start";
156 QStringLiteral("Audio element resource error"));
157 return;
158 }
159 if (!m_source.isEmpty()) {
160 pause();
161 m_audio.set("currentTime", emscripten::val(0));
162 }
163 if (m_audioIODevice) {
164 m_audioIODevice->close();
165 delete m_audioIODevice;
166 m_audioIODevice = 0;
167 }
168}
169
171{
172 if (m_audio.isNull() || m_audio.isUndefined()) {
173 qCDebug(qWasmMediaAudioOutput) << "audio failed to start";
175 QStringLiteral("Audio element resource error"));
176 return;
177 }
178 m_audio.call<emscripten::val>("pause");
179}
180
181void QWasmAudioOutput::createAudioElement(const std::string &id)
182{
183 emscripten::val document = emscripten::val::global("document");
184 m_audio = document.call<emscripten::val>("createElement", std::string("audio"));
185
186 // only works in chrome and firefox.
187 // Firefox this feature is behind media.setsinkid.enabled preferences
188 // allows user to choose audio output device
189
190 if (!m_audio.hasOwnProperty("sinkId") || m_audio["sinkId"].isUndefined()) {
191 return;
192 }
193
194 std::string usableId = id;
195 if (usableId.empty())
197
198 qstdweb::PromiseCallbacks sinkIdCallbacks{
199 .thenFunc = [](emscripten::val) { qCWarning(qWasmMediaAudioOutput) << "setSinkId ok"; },
200 .catchFunc =
201 [](emscripten::val) {
202 qCWarning(qWasmMediaAudioOutput) << "Error while trying to setSinkId";
203 }
204 };
205 qstdweb::Promise::make(m_audio, "setSinkId", std::move(sinkIdCallbacks), std::move(usableId));
206
207 m_audio.set("id", usableId.c_str());
208}
209
210void QWasmAudioOutput::doElementCallbacks()
211{
212 // error
213 auto errorCallback = [&](emscripten::val event) {
214 qCDebug(qWasmMediaAudioOutput) << "error";
215 if (event.isUndefined() || event.isNull())
216 return;
217 emit errorOccured(m_audio["error"]["code"].as<int>(),
218 QString::fromStdString(m_audio["error"]["message"].as<std::string>()));
219
221 QString::fromStdString(m_audio["error"]["message"].as<std::string>());
222 if (errorMessage.isEmpty()) {
223 switch (m_audio["error"]["code"].as<int>()) {
225 errorMessage = QStringLiteral("aborted by the user agent at the user's request.");
226 break;
228 errorMessage = QStringLiteral("network error.");
229 break;
231 errorMessage = QStringLiteral("decoding error.");
232 break;
234 errorMessage = QStringLiteral("src attribute not suitable.");
235 break;
236 };
237 }
238 qCDebug(qWasmMediaAudioOutput) << m_audio["error"]["code"].as<int>() << errorMessage;
239
240 emit errorOccured(m_audio["error"]["code"].as<int>(), errorMessage);
241 };
242 m_errorChangeEvent.reset(new qstdweb::EventCallback(m_audio, "error", errorCallback));
243
244 // loadeddata
245 auto loadedDataCallback = [&](emscripten::val event) {
247 qCDebug(qWasmMediaAudioOutput) << "loaded data";
248 qstdweb::window()["URL"].call<emscripten::val>("revokeObjectURL", m_audio["src"]);
249 };
250 m_loadedDataEvent.reset(new qstdweb::EventCallback(m_audio, "loadeddata", loadedDataCallback));
251
252 // canplay
253 auto canPlayCallback = [&](emscripten::val event) {
254 if (event.isUndefined() || event.isNull())
255 return;
256 qCDebug(qWasmMediaAudioOutput) << "can play";
257 emit readyChanged(true);
259 };
260 m_canPlayChangeEvent.reset(new qstdweb::EventCallback(m_audio, "canplay", canPlayCallback));
261
262 // canplaythrough
263 auto canPlayThroughCallback = [&](emscripten::val event) {
266 };
267 m_canPlayThroughChangeEvent.reset(
268 new qstdweb::EventCallback(m_audio, "canplaythrough", canPlayThroughCallback));
269
270 // play
271 auto playCallback = [&](emscripten::val event) {
273 qCDebug(qWasmMediaAudioOutput) << "play";
275 };
276 m_playEvent.reset(new qstdweb::EventCallback(m_audio, "play", playCallback));
277
278 // durationchange
279 auto durationChangeCallback = [&](emscripten::val event) {
280 qCDebug(qWasmMediaAudioOutput) << "durationChange";
281
282 // duration in ms
283 emit durationChanged(event["target"]["duration"].as<double>() * 1000);
284 };
285 m_durationChangeEvent.reset(
286 new qstdweb::EventCallback(m_audio, "durationchange", durationChangeCallback));
287
288 // ended
289 auto endedCallback = [&](emscripten::val event) {
291 qCDebug(qWasmMediaAudioOutput) << "ended";
292 m_currentMediaStatus = QMediaPlayer::EndOfMedia;
293 emit statusChanged(m_currentMediaStatus);
294 };
295 m_endedEvent.reset(new qstdweb::EventCallback(m_audio, "ended", endedCallback));
296
297 // progress (buffering progress)
298 auto progesssCallback = [&](emscripten::val event) {
299 if (event.isUndefined() || event.isNull())
300 return;
301 qCDebug(qWasmMediaAudioOutput) << "progress";
302 float duration = event["target"]["duration"].as<int>();
303 if (duration < 0) // track not exactly ready yet
304 return;
305
306 emscripten::val timeRanges = event["target"]["buffered"];
307
308 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
309 && timeRanges["length"].as<int>() == 1) {
310 emscripten::val dVal = timeRanges.call<emscripten::val>("end", 0);
311
312 if (!dVal.isNull() || !dVal.isUndefined()) {
313 double bufferedEnd = dVal.as<double>();
314
315 if (duration > 0 && bufferedEnd > 0) {
316 float bufferedValue = (bufferedEnd / duration * 100);
317 qCDebug(qWasmMediaAudioOutput) << "progress buffered" << bufferedValue;
318
319 emit bufferingChanged(m_currentBufferedValue);
320 if (bufferedEnd == duration)
321 m_currentMediaStatus = QMediaPlayer::BufferedMedia;
322 else
323 m_currentMediaStatus = QMediaPlayer::BufferingMedia;
324
325 emit statusChanged(m_currentMediaStatus);
326 }
327 }
328 }
329 };
330 m_progressChangeEvent.reset(new qstdweb::EventCallback(m_audio, "progress", progesssCallback));
331
332 // timupdate
333 auto timeUpdateCallback = [&](emscripten::val event) {
334 qCDebug(qWasmMediaAudioOutput)
335 << "timeupdate" << (event["target"]["currentTime"].as<double>() * 1000);
336
337 // qt progress is ms
338 emit progressChanged(event["target"]["currentTime"].as<double>() * 1000);
339 };
340 m_timeUpdateEvent.reset(new qstdweb::EventCallback(m_audio, "timeupdate", timeUpdateCallback));
341
342 // pause
343 auto pauseCallback = [&](emscripten::val event) {
345 qCDebug(qWasmMediaAudioOutput) << "pause";
346
347 int currentTime = m_audio["currentTime"].as<int>(); // in seconds
348 int duration = m_audio["duration"].as<int>(); // in seconds
349 if ((currentTime > 0 && currentTime < duration)) {
351 } else {
353 }
354 };
355 m_pauseChangeEvent.reset(new qstdweb::EventCallback(m_audio, "pause", pauseCallback));
356}
357
The QAudioDevice class provides an information about audio devices and their functionality.
QByteArray id
\qmlproperty string QtMultimedia::audioDevice::id
\qmltype AudioOutput \instantiates QAudioOutput
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
std::string toStdString() const
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
\inmodule QtCore \reentrant
Definition qiodevice.h:34
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
virtual void close()
First emits aboutToClose(), then closes the device and sets its OpenMode to NotOpen.
QAudioDevice defaultAudioOutput
\qmlproperty audioDevice QtMultimedia::MediaDevices::defaultAudioOutput Returns the default audio out...
\inmodule QtCore
QMimeType mimeTypeForData(const QByteArray &data) const
Returns a MIME type for data.
QMimeType mimeTypeForFile(const QString &fileName, MatchMode mode=MatchDefault) const
Returns a MIME type for the file named fileName using mode.
\inmodule QtCore
Definition qmimetype.h:25
QString name
the name of the MIME type
Definition qmimetype.h:29
void reset(T *other=nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval< T * >())))
Deletes the existing object it is pointing to (if any), and sets its pointer to other.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromStdString(const std::string &s)
Definition qstring.h:1322
std::string toStdString() const
Returns a std::string object with the data contained in this QString.
Definition qstring.h:1319
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
\inmodule QtCore
Definition qurl.h:94
bool isLocalFile() const
Definition qurl.cpp:3431
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1888
QString toString(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2828
QString toLocalFile() const
Returns the path of this URL formatted as a local file path.
Definition qurl.cpp:3411
void progressChanged(qint32 position)
void bufferingChanged(qint32 percent)
void errorOccured(qint32 code, const QString &message)
void setMuted(bool muted) override
void stateChanged(QWasmMediaPlayer::QWasmMediaPlayerState newState)
void statusChanged(QMediaPlayer::MediaStatus status)
void readyChanged(bool)
void durationChanged(qint64 duration)
void setVolume(float volume) override
void setAudioDevice(const QAudioDevice &device) final
void setSource(const QUrl &url)
emscripten::val val()
Definition qstdweb.cpp:535
static Blob copyFrom(const char *buffer, uint32_t size, std::string mimeType)
Definition qstdweb.cpp:518
Combined button and popup list for selecting options.
void make(emscripten::val target, QString methodName, PromiseCallbacks callbacks, Args... args)
Definition qstdweb_p.h:182
emscripten::val window()
Definition qstdweb_p.h:205
#define Q_FUNC_INFO
EGLStreamKHR stream
const char * mimeType
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
GLenum GLuint id
[7]
struct _cl_event * event
#define QStringLiteral(str)
#define emit
#define Q_UNUSED(x)
double qreal
Definition qtypes.h:92
static QString errorMessage(QUrlPrivate::ErrorCode errorCode, const QString &errorSource, qsizetype errorPosition)
Definition qurl.cpp:3503
static double currentTime()
QFileInfo info(fileName)
[8]
QUrl url("example.com")
[constructor-url-reference]
QMimeDatabase db
[0]
std::function< void(emscripten::val)> thenFunc
Definition qstdweb_p.h:173
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent