* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with License.
* You may obtain a copy of License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "oh_audio_play.h"
#include <cstdint>
#include <hilog/log.h>
#include <unistd.h>
#include <algorithm>
#include <chrono>
#include "ohaudio/native_audio_session_manager.h"
#include "audio_constants.h"
#include "securec.h"
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN AudioConstants::LOG_DOMAIN
#define LOG_TAG "OH_AUDIO_PLAY"
void OnAudioInterruptEvent(OH_AudioRenderer *audioRenderer, [[maybe_unused]] void *userData,
OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint) {
auto &player = OHAudioPlayer::GetInstance();
if (hint == AUDIOSTREAM_INTERRUPT_HINT_PAUSE) {
OH_LOG_INFO(LOG_APP, "Audio paused by interrupt.");
player.isPaused.store(true);
auto now = std::chrono::system_clock::now().time_since_epoch();
player.pauseStartTime.store(std::chrono::duration_cast<std::chrono::milliseconds>(now).count());
player.PlayStatusCallback(AudioConstants::PlayStatus::Pause);
} else if (hint == AUDIOSTREAM_INTERRUPT_HINT_STOP) {
OH_LOG_INFO(LOG_APP, "Audio stopped by interrupt.");
player.PlayStatusCallback(AudioConstants::PlayStatus::Pause);
} else if ((type == AUDIOSTREAM_INTERRUPT_SHARE) && (hint == AUDIOSTREAM_INTERRUPT_HINT_RESUME)) {
OH_LOG_INFO(LOG_APP, "Audio resume interrupt received.");
if (player.isPaused.load()) {
auto now = std::chrono::system_clock::now().time_since_epoch();
int64_t resumeTime = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
int64_t pauseTime = player.pauseStartTime.load();
int64_t elapsedMs = resumeTime - pauseTime;
OH_LOG_INFO(LOG_APP, "Audio resume after %{public}ld ms.", elapsedMs);
player.isPaused.store(false);
if (elapsedMs <= player.RESUME_THRESHOLD_MS) {
OH_LOG_INFO(LOG_APP, "Resume within 15s, resuming playback.");
player.PlayStatusCallback(AudioConstants::PlayStatus::Play);
} else {
OH_LOG_INFO(LOG_APP, "Resume after 15s, not resuming playback.");
}
}
} else if (hint == AUDIOSTREAM_INTERRUPT_HINT_MUTE) {
OH_LOG_INFO(LOG_APP, "Audio muted, stopping progress update.");
player.isMuted.store(true);
player.mutePosition.store(player.GetCurrentPosition());
auto now = std::chrono::system_clock::now().time_since_epoch();
player.muteStartTime.store(std::chrono::duration_cast<std::chrono::milliseconds>(now).count());
player.PlayStatusCallback(AudioConstants::PlayStatus::Muted);
} else if (hint == AUDIOSTREAM_INTERRUPT_HINT_UNMUTE) {
if (player.isMuted.load()) {
auto now = std::chrono::system_clock::now().time_since_epoch();
int64_t unmuteTime = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
int64_t muteTime = player.muteStartTime.load();
int64_t elapsedMs = unmuteTime - muteTime;
player.isMuted.store(false);
if (elapsedMs <= player.RESUME_THRESHOLD_MS) {
OH_LOG_INFO(LOG_APP, "Unmute within 15s, notifying unmuted state.");
player.PlayStatusCallback(AudioConstants::PlayStatus::UnMuted);
} else {
OH_LOG_INFO(LOG_APP, "Unmute after 15s, preparing position and notifying pause state.");
uint32_t position = player.mutePosition.load();
player.PreparePosition(position);
player.PlayStatusCallback(AudioConstants::PlayStatus::Pause);
}
}
}
}
void OnAudioErrorEvent([[maybe_unused]] OH_AudioRenderer *audioRenderer, [[maybe_unused]] void *userData,
OH_AudioStream_Result error) {
OH_LOG_ERROR(LOG_APP, "Audio render error, ret: %{public}d", error);
}
void OnAudioOutputDeviceChangeEvent([[maybe_unused]] OH_AudioRenderer *audioRenderer,
[[maybe_unused]] void *userData, OH_AudioStream_DeviceChangeReason reason) {
OH_LOG_INFO(LOG_APP, "Audio output device changed.");
}
OH_AudioData_Callback_Result OnAudioRendererWriteDataEvent([[maybe_unused]] OH_AudioRenderer *audioRenderer,
void *userData, void *audioData, int32_t audioDataSize) {
auto audioBookFileInfo = reinterpret_cast<AudioBookFileInfo *>(userData);
auto &player = OHAudioPlayer::GetInstance();
int64_t remainedSize = audioBookFileInfo->audioBookFileSize - audioBookFileInfo->currentPlayOffset;
int32_t bytesToRead = std::min(static_cast<int64_t>(audioDataSize), remainedSize);
size_t actualRead = player.bufferQueue->Read(audioData, bytesToRead);
if (actualRead < 0) {
OH_LOG_ERROR(LOG_APP, "Read audio data error.");
return AUDIO_DATA_CALLBACK_RESULT_INVALID;
}
audioBookFileInfo->currentPlayOffset += actualRead;
int64_t framesProvided = actualRead / AudioConstants::FRAME_SIZE;
player.totalFramesProvided += framesProvided;
if (actualRead == audioDataSize) {
return AUDIO_DATA_CALLBACK_RESULT_VALID;
}
if (actualRead < audioDataSize) {
bool isLastFrame = (audioBookFileInfo->currentPlayOffset >= audioBookFileInfo->audioBookFileSize);
if (isLastFrame) {
memset_s(static_cast<char*>(audioData) + actualRead, audioDataSize - actualRead, 0, audioDataSize - actualRead);
return AUDIO_DATA_CALLBACK_RESULT_VALID;
} else {
player.totalFramesProvided -= framesProvided;
audioBookFileInfo->currentPlayOffset -= actualRead;
OH_LOG_INFO(LOG_APP, "Incomplete frame, will retry. Read: %{public}ld, Requested: %{public}d", actualRead, audioDataSize);
return AUDIO_DATA_CALLBACK_RESULT_INVALID;
}
}
return AUDIO_DATA_CALLBACK_RESULT_VALID;
}
void OHAudioPlayer::InitPlayer() {
if ((audioRenderer != nullptr) || (rendererBuilder != nullptr) || (audioBookFileInfo != nullptr)) {
OH_LOG_INFO(LOG_APP, "Previous audio player or builder or fileInfo remained and release it.");
ReleasePlayer();
}
OH_AudioStream_Type streamType = AUDIOSTREAM_TYPE_RENDERER;
auto ret = OH_AudioStreamBuilder_Create(&rendererBuilder, streamType);
if (ret != AUDIOSTREAM_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "Create stream builder failed, ret: %{public}d", ret);
return;
}
audioBookFileInfo = new AudioBookFileInfo();
(void)OH_AudioStreamBuilder_SetSamplingRate(rendererBuilder, AudioConstants::SAMPLE_RATE);
(void)OH_AudioStreamBuilder_SetChannelCount(rendererBuilder, AudioConstants::CHANNEL_COUNT);
(void)OH_AudioStreamBuilder_SetSampleFormat(rendererBuilder, AUDIOSTREAM_SAMPLE_S16LE);
(void)OH_AudioStreamBuilder_SetEncodingType(rendererBuilder, AUDIOSTREAM_ENCODING_TYPE_RAW);
(void)OH_AudioStreamBuilder_SetRendererInfo(rendererBuilder, AUDIOSTREAM_USAGE_AUDIOBOOK);
(void)OH_AudioStreamBuilder_SetRendererInterruptCallback(rendererBuilder, OnAudioInterruptEvent, nullptr);
(void)OH_AudioStreamBuilder_SetRendererErrorCallback(rendererBuilder, OnAudioErrorEvent, nullptr);
(void)OH_AudioStreamBuilder_SetRendererOutputDeviceChangeCallback(rendererBuilder, OnAudioOutputDeviceChangeEvent, nullptr);
(void)OH_AudioStreamBuilder_SetRendererWriteDataCallback(rendererBuilder, OnAudioRendererWriteDataEvent,
reinterpret_cast<void *>(audioBookFileInfo));
ret = OH_AudioStreamBuilder_GenerateRenderer(rendererBuilder, &audioRenderer);
if (ret != AUDIOSTREAM_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "Create audio renderer failed, ret: %{public}d", ret);
ReleasePlayer();
}
OH_AudioSession_Strategy strategy = {
.concurrencyMode = CONCURRENCY_DEFAULT
};
OH_AudioRenderer_SetIndependentAudioSessionStrategy(audioRenderer, &strategy, 4);
OH_AudioManager_GetAudioResourceManager(&audioResourceManager);
if (audioResourceManager != nullptr) {
OH_AudioResourceManager_CreateWorkgroup(audioResourceManager, "AudioBookWorkgroup", &audioWorkgroup);
OH_LOG_INFO(LOG_APP, "Audio workgroup created");
}
bufferQueue = new AudioBufferQueue(prefetchBufferSize);
OH_LOG_INFO(LOG_APP, "Init player successfully.");
}
void OHAudioPlayer::LoadAudioBookInfo(uint32_t audioBookFd, uint32_t audioBookFileSize, uint32_t audioBookDuration,
uint32_t audioBookFileOffset) {
if (audioBookFileInfo == nullptr) {
OH_LOG_ERROR(LOG_APP, "The audioBookFileInfo is null.");
return;
}
StopPrefetch();
audioBookFileInfo->audioBookFd = audioBookFd;
audioBookFileInfo->audioBookFileSize = audioBookFileSize;
audioBookFileInfo->audioBookDuration = audioBookDuration;
audioBookFileInfo->audioBookFileOffset = audioBookFileOffset;
audioBookFileInfo->currentPlayOffset = 0;
(void)lseek(audioBookFileInfo->audioBookFd, audioBookFileOffset, SEEK_SET);
seekStartPoint = 0;
totalFramesProvided = 0;
isFileLoad = true;
if (bufferQueue != nullptr) {
bufferQueue->Clear();
}
StartPrefetch();
OH_LOG_INFO(LOG_APP,
"Load audio book information successfully. "
"Audio book fd: %{public}d, "
"file size: %{public}d, "
"Audio book duration: %{public}d."
"file offset: %{public}d."
"seekStartPoint: %{public}d",
audioBookFd, audioBookFileSize, audioBookDuration, audioBookFileOffset, seekStartPoint);
}
void OHAudioPlayer::PlayAudioBook() {
if (audioRenderer == nullptr) {
OH_LOG_ERROR(LOG_APP, "The audioRenderer is null.");
return;
}
auto ret = OH_AudioRenderer_Start(audioRenderer);
if (ret != AUDIOSTREAM_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "Play audio book failed, ret: %{public}d", ret);
return;
}
OH_LOG_INFO(LOG_APP, "Play audio book successfully.");
}
void OHAudioPlayer::PauseAudioBook() {
if (audioRenderer == nullptr) {
OH_LOG_ERROR(LOG_APP, "The audioRenderer is null.");
return;
}
auto ret = OH_AudioRenderer_Pause(audioRenderer);
if (ret != AUDIOSTREAM_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "Pause audio book failed, ret: %{public}d", ret);
return;
}
OH_LOG_INFO(LOG_APP, "Pause audio book successfully.");
}
void OHAudioPlayer::StopAudioBook() {
if (audioRenderer == nullptr) {
OH_LOG_ERROR(LOG_APP, "The audioRenderer is null.");
return;
}
auto ret = OH_AudioRenderer_Stop(audioRenderer);
if (ret != AUDIOSTREAM_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "Stop audio book failed, ret: %{public}d", ret);
return;
}
ret = OH_AudioRenderer_Flush(audioRenderer);
if (ret != AUDIOSTREAM_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "Flush audio book failed, ret: %{public}d", ret);
return;
}
OH_LOG_INFO(LOG_APP, "Stop audio book successfully.");
}
uint32_t OHAudioPlayer::GetCurrentPosition() {
if (audioRenderer == nullptr) {
return 0;
}
if (isFileLoad) {
OH_AudioRenderer_Flush(audioRenderer);
isFileLoad = false;
}
int64_t framePosition;
int64_t timestamp;
auto ret = OH_AudioRenderer_GetAudioTimestampInfo(audioRenderer, &framePosition, ×tamp);
if (ret != AUDIOSTREAM_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "Get audio timestamp info failed, ret: %{public}d", ret);
return 0;
}
uint32_t playedBytes = static_cast<uint32_t>(framePosition * AudioConstants::FRAME_SIZE);
OH_LOG_INFO(LOG_APP, "framePosition: %{public}ld "
"playedBytes: %{public}d "
"seekStartPoint: %{public}d", framePosition, playedBytes, seekStartPoint);
return (playedBytes + seekStartPoint);
}
uint32_t OHAudioPlayer::GetDuration() {
if (audioBookFileInfo == nullptr) {
return 0;
}
return audioBookFileInfo->audioBookDuration;
}
uint32_t OHAudioPlayer::GetMutePosition() {
return mutePosition.load();
}
void OHAudioPlayer::PreparePosition(uint32_t position) {
if (audioBookFileInfo == nullptr) {
OH_LOG_ERROR(LOG_APP, "The audioBookFileInfo is null.");
return;
}
if (position > audioBookFileInfo->audioBookFileSize) {
OH_LOG_ERROR(LOG_APP, "Prepare position exceeds file size.");
return;
}
StopPrefetch();
if (audioRenderer != nullptr) {
OH_AudioRenderer_Pause(audioRenderer);
}
if (audioRenderer != nullptr) {
OH_AudioRenderer_Flush(audioRenderer);
}
uint32_t alignedPosition = (position / AudioConstants::FRAME_SIZE) * AudioConstants::FRAME_SIZE;
auto ret = lseek(audioBookFileInfo->audioBookFd, (audioBookFileInfo->audioBookFileOffset + alignedPosition), SEEK_SET);
if (ret == -1) {
OH_LOG_ERROR(LOG_APP, "Prepare position failed.");
return;
}
audioBookFileInfo->currentPlayOffset = alignedPosition;
seekStartPoint = alignedPosition;
totalFramesProvided = 0;
if (bufferQueue != nullptr) {
bufferQueue->Clear();
}
StartPrefetch();
OH_LOG_INFO(LOG_APP, "Prepare position successfully. Position: %{public}u, Aligned: %{public}u", position, alignedPosition);
}
void OHAudioPlayer::SeekTo(uint32_t position) {
PreparePosition(position);
if (audioRenderer != nullptr) {
OH_AudioRenderer_Start(audioRenderer);
}
OH_LOG_INFO(LOG_APP, "Seek to position successfully and started.");
}
void OHAudioPlayer::SetSpeed(float speed) {
if (audioRenderer == nullptr) {
OH_LOG_ERROR(LOG_APP, "The audioRenderer is null.");
return;
}
if (speed < AudioConstants::SPEED_0_25 || speed > AudioConstants::SPEED_4_0) {
OH_LOG_ERROR(LOG_APP, "Invalid speed value. Speed must be between 0.25 and 4.0.");
return;
}
auto ret = OH_AudioRenderer_SetSpeed(audioRenderer, speed);
if (ret != AUDIOSTREAM_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "Set speed failed, ret: %{public}d", ret);
return;
}
OH_LOG_INFO(LOG_APP, "Set speed successfully. Speed: %{public}f", speed);
}
AudioTimestampInfo OHAudioPlayer::GetAudioTimestampInfo() {
AudioTimestampInfo info{0, 0};
if (audioRenderer == nullptr) {
return info;
}
OH_AudioRenderer_GetAudioTimestampInfo(audioRenderer, &info.framePosition, &info.timestamp);
return info;
}
void OHAudioPlayer::ReleasePlayer() {
StopPrefetch();
if (bufferQueue != nullptr) {
delete bufferQueue;
bufferQueue = nullptr;
}
if (audioWorkgroup != nullptr && audioResourceManager != nullptr) {
OH_AudioResourceManager_ReleaseWorkgroup(audioResourceManager, audioWorkgroup);
audioWorkgroup = nullptr;
OH_LOG_INFO(LOG_APP, "Audio workgroup released");
}
if (audioResourceManager != nullptr) {
audioResourceManager = nullptr;
}
if (rendererBuilder != nullptr) {
OH_AudioStreamBuilder_Destroy(rendererBuilder);
rendererBuilder = nullptr;
}
if (audioRenderer != nullptr) {
OH_AudioRenderer_Stop(audioRenderer);
OH_AudioRenderer_Release(audioRenderer);
audioRenderer = nullptr;
}
if (audioBookFileInfo != nullptr) {
delete audioBookFileInfo;
audioBookFileInfo = nullptr;
}
OH_LOG_INFO(LOG_APP, "Release player successfully.");
}
void OHAudioPlayer::StartPrefetch() {
if (isPrefetching.load()) {
return;
}
isPrefetching.store(true);
prefetchThread = new std::thread(&OHAudioPlayer::PrefetchThreadFunc, this);
}
void OHAudioPlayer::StopPrefetch() {
if (!isPrefetching.load()) {
return;
}
isPrefetching.store(false);
if (prefetchThread != nullptr && prefetchThread->joinable()) {
prefetchThread->join();
delete prefetchThread;
prefetchThread = nullptr;
}
if (audioWorkgroup != nullptr && prefetchThreadTokenId != 0) {
OH_AudioWorkgroup_RemoveThread(audioWorkgroup, prefetchThreadTokenId);
prefetchThreadTokenId = 0;
OH_LOG_INFO(LOG_APP, "Prefetch thread removed from workgroup");
}
}
void OHAudioPlayer::PrefetchThreadFunc() {
OH_LOG_INFO(LOG_APP, "Prefetch thread started");
if (audioWorkgroup != nullptr) {
OH_AudioWorkgroup_AddCurrentThread(audioWorkgroup, &prefetchThreadTokenId);
OH_LOG_INFO(LOG_APP, "Prefetch thread added to workgroup, token: %{public}d", prefetchThreadTokenId);
}
while (isPrefetching.load()) {
if (audioWorkgroup != nullptr) {
auto now = std::chrono::system_clock::now().time_since_epoch();
auto startTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
OH_AudioWorkgroup_Start(audioWorkgroup, startTimeMs, startTimeMs + prefetchDelayMs);
}
int64_t remainingFile = audioBookFileInfo->audioBookFileSize -
audioBookFileInfo->currentPlayOffset;
if (remainingFile <= 0) {
OH_LOG_INFO(LOG_APP, "File read complete, stop prefetching");
if (audioWorkgroup != nullptr) {
OH_AudioWorkgroup_Stop(audioWorkgroup);
}
break;
}
if (bufferQueue->IsFull()) {
std::this_thread::sleep_for(std::chrono::milliseconds(AudioConstants::TEN_MILLISECONDS));
if (audioWorkgroup != nullptr) {
OH_AudioWorkgroup_Stop(audioWorkgroup);
}
continue;
}
size_t availableSpace = bufferQueue->GetAvailableSpace();
size_t bytesToRead = std::min(static_cast<size_t>(remainingFile), availableSpace);
std::this_thread::sleep_for(std::chrono::milliseconds(prefetchDelayMs));
bufferQueue->Write(audioBookFileInfo->audioBookFd, bytesToRead);
if (audioWorkgroup != nullptr) {
OH_AudioWorkgroup_Stop(audioWorkgroup);
}
}
OH_LOG_INFO(LOG_APP, "Prefetch thread stopped");
}
int64_t OHAudioPlayer::GetTotalFramesProvided() const {
return totalFramesProvided;
}
int64_t OHAudioPlayer::GetCurrentTotalFramesProvided() const {
uint32_t frameSize = AudioConstants::CHANNEL_COUNT * (AudioConstants::BITS_PER_SAMPLE / 8);
return (seekStartPoint / frameSize) + totalFramesProvided;;
}
int64_t OHAudioPlayer::GetTotalFileFrames() const {
if (audioBookFileInfo == nullptr) {
return 0;
}
uint32_t frameSize = AudioConstants::CHANNEL_COUNT * (AudioConstants::BITS_PER_SAMPLE / 8);
return audioBookFileInfo->audioBookFileSize / frameSize;
}