4#include <qaudiodevice.h>
5#include <qaudiooutput.h>
6#include <qwasmaudiooutput_p.h>
8#include <QMimeDatabase>
9#include <QtCore/qloggingcategory.h>
10#include <QMediaDevices>
13#include <QMimeDatabase>
18using namespace Qt::Literals;
22QWasmAudioOutput::QWasmAudioOutput(QAudioOutput *parent)
23 : QPlatformAudioOutput(parent)
27QWasmAudioOutput::~QWasmAudioOutput() =
default;
29void QWasmAudioOutput::setAudioDevice(
const QAudioDevice &audioDevice)
31 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
35void QWasmAudioOutput::setVideoElement(emscripten::val videoElement)
37 m_videoElement = videoElement;
40emscripten::val QWasmAudioOutput::videoElement()
42 return m_videoElement;
45void QWasmAudioOutput::setMuted(
bool muted)
47 emscripten::val realElement = videoElement();
48 if (!realElement.isUndefined()) {
49 realElement.set(
"muted", muted);
52 if (m_audio.isUndefined() || m_audio.isNull()) {
53 qCDebug(qWasmMediaAudioOutput) <<
"Error"
54 <<
"Audio element could not be created";
55 emit errorOccured(QMediaPlayer::ResourceError,
56 QStringLiteral(
"Media file could not be opened"));
59 m_audio.set(
"mute", muted);
62void QWasmAudioOutput::setVolume(
float volume)
64 volume = qBound(qreal(0.0), volume, qreal(1.0));
65 emscripten::val realElement = videoElement();
66 if (!realElement.isUndefined()) {
67 realElement.set(
"volume", volume);
70 if (m_audio.isUndefined() || m_audio.isNull()) {
71 qCDebug(qWasmMediaAudioOutput) <<
"Error"
72 <<
"Audio element not available";
73 emit errorOccured(QMediaPlayer::ResourceError,
74 QStringLiteral(
"Media file could not be opened"));
78 m_audio.set(
"volume", volume);
81void QWasmAudioOutput::setSource(
const QUrl &url)
83 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << url;
89 createAudioElement(device.id().toStdString());
91 if (m_audio.isUndefined() || m_audio.isNull()) {
92 qCDebug(qWasmMediaAudioOutput) <<
"Error"
93 <<
"Audio element could not be created";
94 emit errorOccured(QMediaPlayer::ResourceError,
95 QStringLiteral(
"Audio element could not be created"));
99 emscripten::val document = emscripten::val::global(
"document");
100 emscripten::val body = document[
"body"];
102 m_audio.set(
"id", device.id().toStdString());
104 body.call<
void>(
"appendChild", m_audio);
107 if (url.isLocalFile()) {
108 qCDebug(qWasmMediaAudioOutput) <<
"is localfile";
109 m_source = url.toLocalFile();
111 QFile mediaFile(m_source);
112 if (!mediaFile.exists()) {
113 qCDebug(qWasmMediaAudioOutput) <<
"Error"
114 <<
"Media file does not exist";
115 QMetaObject::invokeMethod(
this, &QWasmAudioOutput::errorOccured, Qt::QueuedConnection,
116 QMediaPlayer::ResourceError,
117 QStringLiteral(
"Media file does not exist"));
122 if (!mediaFile.open(QIODevice::ReadOnly)) {
123 qCDebug(qWasmMediaAudioOutput) <<
"Error"
124 <<
"Media file could not be opened";
125 emit errorOccured(QMediaPlayer::ResourceError,
126 QStringLiteral(
"Media file could not be opened"));
131 QByteArray content = mediaFile.readAll();
134 qCDebug(qWasmMediaAudioOutput) << db.mimeTypeForData(content).name();
136 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content.constData(), content.size());
137 emscripten::val contentUrl =
138 qstdweb::window()[
"URL"].call<emscripten::val>(
"createObjectURL", contentBlob.val());
140 emscripten::val audioSourceElement =
141 document.call<emscripten::val>(
"createElement", std::string(
"source"));
143 audioSourceElement.set(
"src", contentUrl);
146 QFileInfo info(m_source);
147 QMimeType mimeType = db.mimeTypeForFile(info);
149 audioSourceElement.set(
"type", mimeType.name().toStdString());
150 m_audio.call<
void>(
"appendChild", audioSourceElement);
152 m_audio.call<
void>(
"setAttribute", emscripten::val(
"srcObject"), contentUrl);
155 m_source = url.toString();
156 m_audio.set(
"src", m_source.toStdString());
158 m_audio.set(
"id", device.id().toStdString());
160 body.call<
void>(
"appendChild", m_audio);
161 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
163 doElementCallbacks();
166void QWasmAudioOutput::setSource(QIODevice *stream)
168 m_audioIODevice = stream;
171void QWasmAudioOutput::start()
173 if (m_audio.isNull() || m_audio.isUndefined()) {
174 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
175 emit errorOccured(QMediaPlayer::ResourceError,
176 QStringLiteral(
"Audio element resource error"));
180 m_audio.call<
void>(
"play");
183void QWasmAudioOutput::stop()
185 if (m_audio.isNull() || m_audio.isUndefined()) {
186 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
187 emit errorOccured(QMediaPlayer::ResourceError,
188 QStringLiteral(
"Audio element resource error"));
191 if (!m_source.isEmpty()) {
193 m_audio.set(
"currentTime", emscripten::val(0));
195 if (m_audioIODevice) {
196 m_audioIODevice->close();
197 delete m_audioIODevice;
202void QWasmAudioOutput::pause()
204 if (m_audio.isNull() || m_audio.isUndefined()) {
205 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
206 emit errorOccured(QMediaPlayer::ResourceError,
207 QStringLiteral(
"Audio element resource error"));
210 m_audio.call<emscripten::val>(
"pause");
213void QWasmAudioOutput::createAudioElement(
const std::string &id)
215 emscripten::val document = emscripten::val::global(
"document");
216 m_audio = document.call<emscripten::val>(
"createElement", std::string(
"audio"));
218 if (id ==
"System output") {
219 emscripten::val audioContext = emscripten::val::global(
"window")[
"AudioContext"].new_();
220 emscripten::val sourceNode = audioContext.call<emscripten::val>(
"createMediaElementSource", m_audio);
222 sourceNode.call<
void>(
"connect", audioContext[
"destination"]);
233 if (!m_audio.hasOwnProperty(
"sinkId") || m_audio[
"sinkId"].isUndefined()) {
237 std::string usableId = id;
238 if (usableId.empty() || usableId ==
"System output")
239 usableId = QMediaDevices::defaultAudioOutput().id();
241 qstdweb::PromiseCallbacks sinkIdCallbacks{
242 .thenFunc = [](emscripten::val) { qCWarning(qWasmMediaAudioOutput) <<
"setSinkId ok"; },
244 [](emscripten::val) {
245 qCWarning(qWasmMediaAudioOutput) <<
"Error while trying to setSinkId";
247 .finallyFunc =
nullptr
249 qstdweb::Promise::make(m_audio, u"setSinkId"_s, std::move(sinkIdCallbacks), std::move(usableId));
251 m_audio.set(
"id", usableId.c_str());
254void QWasmAudioOutput::doElementCallbacks()
257 auto errorCallback = [&](emscripten::val event) {
258 qCDebug(qWasmMediaAudioOutput) <<
"error";
259 if (event.isUndefined() || event.isNull())
261 emit errorOccured(m_audio[
"error"][
"code"].as<
int>(),
262 QString::fromStdString(m_audio[
"error"][
"message"].as<std::string>()));
264 QString errorMessage =
265 QString::fromStdString(m_audio[
"error"][
"message"].as<std::string>());
266 if (errorMessage.isEmpty()) {
267 switch (m_audio[
"error"][
"code"].as<
int>()) {
268 case AudioElementError::MEDIA_ERR_ABORTED:
269 errorMessage = QStringLiteral(
"aborted by the user agent at the user's request.");
271 case AudioElementError::MEDIA_ERR_NETWORK:
272 errorMessage = QStringLiteral(
"network error.");
274 case AudioElementError::MEDIA_ERR_DECODE:
275 errorMessage = QStringLiteral(
"decoding error.");
277 case AudioElementError::MEDIA_ERR_SRC_NOT_SUPPORTED:
278 errorMessage = QStringLiteral(
"src attribute not suitable.");
282 qCDebug(qWasmMediaAudioOutput) << m_audio[
"error"][
"code"].as<
int>() << errorMessage;
284 emit errorOccured(m_audio[
"error"][
"code"].as<
int>(), errorMessage);
286 m_errorChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"error", errorCallback));
289 auto loadedDataCallback = [&](emscripten::val event) {
291 qCDebug(qWasmMediaAudioOutput) <<
"loaded data";
292 qstdweb::window()[
"URL"].call<emscripten::val>(
"revokeObjectURL", m_audio[
"src"]);
294 m_loadedDataEvent.reset(
new qstdweb::EventCallback(m_audio,
"loadeddata", loadedDataCallback));
297 auto canPlayCallback = [&](emscripten::val event) {
298 if (event.isUndefined() || event.isNull())
300 qCDebug(qWasmMediaAudioOutput) <<
"can play";
301 emit readyChanged(
true);
302 emit stateChanged(QWasmMediaPlayer::Preparing);
304 m_canPlayChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"canplay", canPlayCallback));
307 auto canPlayThroughCallback = [&](emscripten::val event) {
309 emit stateChanged(QWasmMediaPlayer::Prepared);
311 m_canPlayThroughChangeEvent.reset(
312 new qstdweb::EventCallback(m_audio,
"canplaythrough", canPlayThroughCallback));
315 auto playCallback = [&](emscripten::val event) {
317 qCDebug(qWasmMediaAudioOutput) <<
"play";
318 emit stateChanged(QWasmMediaPlayer::Started);
320 m_playEvent.reset(
new qstdweb::EventCallback(m_audio,
"play", playCallback));
323 auto durationChangeCallback = [&](emscripten::val event) {
324 qCDebug(qWasmMediaAudioOutput) <<
"durationChange";
327 emit durationChanged(event[
"target"][
"duration"].as<
double>() * 1000);
329 m_durationChangeEvent.reset(
330 new qstdweb::EventCallback(m_audio,
"durationchange", durationChangeCallback));
333 auto endedCallback = [&](emscripten::val event) {
335 qCDebug(qWasmMediaAudioOutput) <<
"ended";
336 m_currentMediaStatus = QMediaPlayer::EndOfMedia;
337 emit statusChanged(m_currentMediaStatus);
339 m_endedEvent.reset(
new qstdweb::EventCallback(m_audio,
"ended", endedCallback));
342 auto progesssCallback = [&](emscripten::val event) {
343 if (event.isUndefined() || event.isNull())
345 qCDebug(qWasmMediaAudioOutput) <<
"progress";
346 float duration = event[
"target"][
"duration"].as<
int>();
350 emscripten::val timeRanges = event[
"target"][
"buffered"];
352 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
353 && timeRanges[
"length"].as<
int>() == 1) {
354 emscripten::val dVal = timeRanges.call<emscripten::val>(
"end", 0);
356 if (!dVal.isNull() || !dVal.isUndefined()) {
357 double bufferedEnd = dVal.as<
double>();
359 if (duration > 0 && bufferedEnd > 0) {
360 float bufferedValue = (bufferedEnd / duration * 100);
361 qCDebug(qWasmMediaAudioOutput) <<
"progress buffered" << bufferedValue;
363 emit bufferingChanged(m_currentBufferedValue);
364 if (bufferedEnd == duration)
365 m_currentMediaStatus = QMediaPlayer::BufferedMedia;
367 m_currentMediaStatus = QMediaPlayer::BufferingMedia;
369 emit statusChanged(m_currentMediaStatus);
374 m_progressChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"progress", progesssCallback));
377 auto timeUpdateCallback = [&](emscripten::val event) {
378 qCDebug(qWasmMediaAudioOutput)
379 <<
"timeupdate" << (event[
"target"][
"currentTime"].as<
double>() * 1000);
382 emit progressChanged(event[
"target"][
"currentTime"].as<
double>() * 1000);
384 m_timeUpdateEvent.reset(
new qstdweb::EventCallback(m_audio,
"timeupdate", timeUpdateCallback));
387 auto pauseCallback = [&](emscripten::val event) {
389 qCDebug(qWasmMediaAudioOutput) <<
"pause";
391 int currentTime = m_audio[
"currentTime"].as<
int>();
392 int duration = m_audio[
"duration"].as<
int>();
393 if ((currentTime > 0 && currentTime < duration)) {
394 emit stateChanged(QWasmMediaPlayer::Paused);
396 emit stateChanged(QWasmMediaPlayer::Stopped);
399 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)