#include "aomusicplayer.h" #include "file_functions.h" #include "networkmanager.h" #include "options.h" #include #include #include #include #include #include AOMusicPlayer::AOMusicPlayer(AOApplication *ao_app) : ao_app(ao_app) {} AOMusicPlayer::~AOMusicPlayer() { for (int i = 0; i < STREAM_COUNT; ++i) { stop(i); } } void AOMusicPlayer::setMuted(bool enabled) { m_muted = enabled; for (int i = 0; i < STREAM_COUNT; ++i) { setStreamVolume(m_volume[i], i); } } void AOMusicPlayer::setStreamVolume(int value, int streamId) { if (!ensureValidStreamId(streamId)) { qWarning().noquote() << QObject::tr("Invalid stream ID '%2'").arg(streamId); return; } m_volume[streamId] = m_muted ? 0.0f : qBound(0.0f, value / 100.0f, 1.0f); ma_sound_set_volume(&m_stream[streamId].sound, m_volume[streamId]); } bool AOMusicPlayer::ensureValidStreamId(int streamId) { return streamId >= 0 && streamId < STREAM_COUNT; } void AOMusicPlayer::play_from_url(const QString &url, int id, bool looping, int effect_flags) { auto &s = m_stream[id]; stop(id); if (!Options::getInstance().streamingEnabled()) { emit track_ready(QStringLiteral("[DISABLED] Streaming disabled")); return; } auto url_s = QUrl(url); url_s.setScheme("https"); // Reusing network manager that already exists in ao_app. s.reply = ao_app->net_manager->get_audio_url(url_s); QVariant len = s.reply->header(QNetworkRequest::ContentLengthHeader); bool ok = false; qint64 content_length = len.toLongLong(&ok); static const qint64 max_stream_size = 256 * 1024 * 1024; if (ok && content_length > max_stream_size) { s.reply->abort(); s.reply->deleteLater(); emit track_ready(QStringLiteral("[ERROR] Stream exceeds maximum size, refusing to download")); return; } if (ok && content_length > 0) { m_stream[id].buffer.reserve(content_length); } connect(s.reply, &QIODevice::readyRead, this, [this, id] { auto &s = m_stream[id]; QByteArray chunk = s.reply->readAll(); if (s.buffer.size() + chunk.size() > max_stream_size) { s.reply->abort(); s.reply->deleteLater(); s.reply = nullptr; s.buffer.clear(); emit track_ready(QStringLiteral("[ERROR] Attempted to download beyond buffer size, aborting")); return; } s.buffer.append(chunk); }); connect(s.reply, &QNetworkReply::finished, this, [this, id, looping] { auto &s = m_stream[id]; on_url_download_finished(id, s.reply, looping); }); } void AOMusicPlayer::on_url_download_finished(int id, QNetworkReply *reply, bool looping) { auto &s = m_stream[id]; if (reply->error() != QNetworkReply::NoError) { reply->deleteLater(); emit track_ready(QStringLiteral("[ERROR] Unable to stream audio due to network error")); return; } if (ma_decoder_init_memory(s.buffer.data(), s.buffer.size(), &ao_app->mus_decoder_config, &s.decoder) != MA_SUCCESS) { reply->deleteLater(); emit track_ready(QStringLiteral("[ERROR] Invalid audio format (stream decoding failed)")); return; } s.has_decoder = true; ma_uint32 flags = m_flags | (looping ? MA_SOUND_FLAG_LOOPING : 0); if (ma_sound_init_from_data_source(&ao_app->audio_engine, &s.decoder, flags, nullptr, &s.sound) != MA_SUCCESS) { reply->deleteLater(); emit track_ready(QStringLiteral("[ERROR] Failed to initialize audio")); return; } start_playback(id); QString track_path = QUrl(reply->url()).path(); reply->deleteLater(); emit track_ready(QString("[STREAM] %1").arg(QFileInfo(track_path).completeBaseName())); } void AOMusicPlayer::start_playback(int id) { auto &s = m_stream[id]; ma_sound_set_volume(&s.sound, m_volume[id]); ma_sound_start(&s.sound); s.state = Stream::playing; } void AOMusicPlayer::stop(int id) { auto &s = m_stream[id]; if (s.state == Stream::playing) { ma_sound_stop(&s.sound); ma_sound_uninit(&s.sound); } if (s.has_decoder) { ma_decoder_uninit(&s.decoder); s.has_decoder = false; } if (s.reply) { s.reply->abort(); s.reply->deleteLater(); s.reply = nullptr; } s.buffer.clear(); s.state = Stream::standby; } void AOMusicPlayer::play_from_file(const QString &path, int id, bool looping, int effect_flags) { auto &s = m_stream[id]; stop(id); ma_uint32 flags = m_flags | (looping ? MA_SOUND_FLAG_LOOPING : 0); bool is_stop = (path == "~stop.mp3"); // Temporary is_stop stub. // Fades and effects are to be considered when implemented. if (is_stop) { emit track_ready(QStringLiteral("None")); return; } QString f_path = ao_app->get_real_path(ao_app->get_music_path(path)); if (ma_sound_init_from_file(&ao_app->audio_engine, qPrintable(f_path), flags, nullptr, nullptr, &s.sound) != MA_SUCCESS) { emit track_ready(QStringLiteral("[ERROR] Failed to initialize sound")); return; } start_playback(id); emit track_ready(QFileInfo(f_path).completeBaseName()); }