From 282bfad1a58fdda245518fd632e92203954c17b4 Mon Sep 17 00:00:00 2001 From: Seungsoo Lee Date: Mon, 21 Sep 2026 21:35:40 +0900 Subject: [PATCH 1/5] [audioplayers] Fix seek completion handling --- packages/audioplayers/CHANGELOG.md | 5 + packages/audioplayers/README.md | 4 +- packages/audioplayers/pubspec.yaml | 2 +- .../audioplayers/tizen/src/audio_player.cc | 122 ++++++++++++------ .../audioplayers/tizen/src/audio_player.h | 4 + 5 files changed, 94 insertions(+), 43 deletions(-) diff --git a/packages/audioplayers/CHANGELOG.md b/packages/audioplayers/CHANGELOG.md index 737fe7bf6..0142186a1 100644 --- a/packages/audioplayers/CHANGELOG.md +++ b/packages/audioplayers/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.1.9 + +* Serialize seeks and defer playback controls until seeking completes. +* Avoid missing network seek completion callbacks after stop by rewinding while paused. + ## 3.1.8 * Update audioplayers to 6.8.1. diff --git a/packages/audioplayers/README.md b/packages/audioplayers/README.md index 52c8cb57f..c51cfc974 100644 --- a/packages/audioplayers/README.md +++ b/packages/audioplayers/README.md @@ -11,7 +11,7 @@ This package is not an _endorsed_ implementation of `audioplayers`. Therefore, y ```yaml dependencies: audioplayers: ^6.8.1 - audioplayers_tizen: ^3.1.8 + audioplayers_tizen: ^3.1.9 ``` @@ -70,4 +70,4 @@ For detailed information on Tizen privileges, see [Tizen Docs: API Privileges](h - `onPlayerComplete` event will not be fired when `ReleaseMode` is set to loop which differs from the behavior specified in the [documentation](https://pub.dev/documentation/audioplayers/latest/audioplayers/AudioPlayer/onPlayerComplete.html). And playback rate will reset to 1.0 when audio is replayed. - `setVolume` will have no effect on TV devices. -- `setPlaybackRate` is limited to values between 0.5 and 2.0 on TV and is not supported on RPI. +- `setPlaybackRate` supports local and streaming sources between 0.5 and 2.0 on TV. On RPI, it supports local files between 0.5 and 5.0, but not streaming sources. diff --git a/packages/audioplayers/pubspec.yaml b/packages/audioplayers/pubspec.yaml index b228f55d9..e2266b556 100644 --- a/packages/audioplayers/pubspec.yaml +++ b/packages/audioplayers/pubspec.yaml @@ -2,7 +2,7 @@ name: audioplayers_tizen description: Tizen implementation of the audioplayers plugin. homepage: https://github.com/flutter-tizen/plugins repository: https://github.com/flutter-tizen/plugins/tree/main/packages/audioplayers -version: 3.1.8 +version: 3.1.9 environment: sdk: ^3.6.0 diff --git a/packages/audioplayers/tizen/src/audio_player.cc b/packages/audioplayers/tizen/src/audio_player.cc index 3c4897b0c..06f652dd6 100644 --- a/packages/audioplayers/tizen/src/audio_player.cc +++ b/packages/audioplayers/tizen/src/audio_player.cc @@ -15,6 +15,7 @@ namespace { struct IdleData { AudioPlayer *player; std::shared_ptr is_alive; + unsigned int generation; }; } // namespace @@ -50,6 +51,10 @@ AudioPlayer::~AudioPlayer() { } void AudioPlayer::Play() { + if (seeking_) { + pending_action_ = PendingAction::kPlay; + return; + } player_state_e state = GetPlayerState(); if (state == PLAYER_STATE_IDLE && preparing_) { // Player is preparing, play will be called in prepared callback. @@ -97,6 +102,10 @@ void AudioPlayer::Play() { } void AudioPlayer::Pause() { + if (seeking_) { + pending_action_ = PendingAction::kPause; + return; + } if (GetPlayerState() == PLAYER_STATE_PLAYING) { int ret = player_pause(player_); if (ret != PLAYER_ERROR_NONE) { @@ -108,30 +117,27 @@ void AudioPlayer::Pause() { } void AudioPlayer::Stop() { + should_play_ = false; + if (release_mode_ == ReleaseMode::kRelease) { + ReleaseMediaSource(); + return; + } + if (seeking_) { + pending_action_ = PendingAction::kPause; + should_seek_to_ = 0; + return; + } player_state_e state = GetPlayerState(); if (state == PLAYER_STATE_PLAYING || state == PLAYER_STATE_PAUSED) { - int ret = player_stop(player_); - if (ret != PLAYER_ERROR_NONE) { - throw AudioPlayerError("player_stop failed", get_error_message(ret)); - } - // Reset the play position to 0 to match other platforms, per the - // AudioPlayer.stop() contract: - // https://pub.dev/documentation/audioplayers/latest/audioplayers/AudioPlayer/stop.html - // This is best-effort: on some devices (e.g. TV with network sources) - // player_set_play_position right after stop can fail with an invalid - // state, which must not crash the app. + Pause(); try { Seek(0); } catch (const AudioPlayerError &error) { OnLog("Failed to reset position on stop: " + error.message()); } - } - - should_play_ = false; - seeking_ = false; - - if (release_mode_ == ReleaseMode::kRelease) { - ReleaseMediaSource(); + } else if (state == PLAYER_STATE_READY) { + ResetPlayer(); + PreparePlayer(); } } @@ -143,6 +149,7 @@ void AudioPlayer::ReleaseMediaSource() { void AudioPlayer::Seek(int32_t position) { if (seeking_) { + should_seek_to_ = position; return; } @@ -359,6 +366,12 @@ void AudioPlayer::ResetPlayer() { } break; } + ++generation_; + preparing_ = false; + seeking_ = false; + should_play_ = false; + should_seek_to_ = -1; + pending_action_ = PendingAction::kNone; } player_state_e AudioPlayer::GetPlayerState() { @@ -381,7 +394,7 @@ void AudioPlayer::OnPrepared(void *data) { G_PRIORITY_DEFAULT_IDLE, [](gpointer data) -> gboolean { auto *idle = static_cast(data); - if (!*idle->is_alive) { + if (!*idle->is_alive || idle->generation != idle->player->generation_) { return G_SOURCE_REMOVE; } auto *player = idle->player; @@ -396,17 +409,7 @@ void AudioPlayer::OnPrepared(void *data) { } player_set_playback_rate(player->player_, player->playback_rate_); - if (player->should_play_) { - int ret = player_start(player->player_); - if (ret != PLAYER_ERROR_NONE) { - player->log_listener_(player->player_id_, "player_start failed."); - return G_SOURCE_REMOVE; - } - player->StartPositionUpdates(); - player->should_play_ = false; - } - - if (player->should_seek_to_ > 0) { + if (player->should_seek_to_ >= 0) { player->seeking_ = true; int ret = player_set_play_position(player->player_, player->should_seek_to_, @@ -418,10 +421,25 @@ void AudioPlayer::OnPrepared(void *data) { return G_SOURCE_REMOVE; } player->should_seek_to_ = -1; + if (player->should_play_) { + player->pending_action_ = PendingAction::kPlay; + player->should_play_ = false; + } + return G_SOURCE_REMOVE; + } + + if (player->should_play_) { + int ret = player_start(player->player_); + if (ret != PLAYER_ERROR_NONE) { + player->log_listener_(player->player_id_, "player_start failed."); + return G_SOURCE_REMOVE; + } + player->StartPositionUpdates(); + player->should_play_ = false; } return G_SOURCE_REMOVE; }, - new IdleData{self, self->is_alive_}, + new IdleData{self, self->is_alive_, self->generation_}, [](gpointer data) { delete static_cast(data); }); } @@ -434,15 +452,37 @@ void AudioPlayer::OnSeekCompleted(void *data) { G_PRIORITY_DEFAULT_IDLE, [](gpointer data) -> gboolean { auto *idle = static_cast(data); - if (!*idle->is_alive) { + if (!*idle->is_alive || idle->generation != idle->player->generation_) { return G_SOURCE_REMOVE; } auto *player = idle->player; - player->seek_completed_listener_(player->player_id_); player->seeking_ = false; + try { + if (player->should_seek_to_ >= 0) { + int position = player->should_seek_to_; + player->should_seek_to_ = -1; + player->Seek(position); + return G_SOURCE_REMOVE; + } + auto action = player->pending_action_; + player->pending_action_ = PendingAction::kNone; + switch (action) { + case PendingAction::kPlay: + player->Play(); + break; + case PendingAction::kPause: + player->Pause(); + break; + case PendingAction::kNone: + break; + } + player->seek_completed_listener_(player->player_id_); + } catch (const AudioPlayerError &error) { + player->OnLog(error.code() + ": " + error.message()); + } return G_SOURCE_REMOVE; }, - new IdleData{self, self->is_alive_}, + new IdleData{self, self->is_alive_, self->generation_}, [](gpointer data) { delete static_cast(data); }); } @@ -455,12 +495,11 @@ void AudioPlayer::OnPlayCompleted(void *data) { G_PRIORITY_DEFAULT_IDLE, [](gpointer data) -> gboolean { auto *idle = static_cast(data); - if (!*idle->is_alive) { + if (!*idle->is_alive || idle->generation != idle->player->generation_) { return G_SOURCE_REMOVE; } auto *player = idle->player; try { - player->Seek(0); player->Stop(); player->play_completed_listener_(player->player_id_); } catch (const AudioPlayerError &error) { @@ -468,7 +507,7 @@ void AudioPlayer::OnPlayCompleted(void *data) { } return G_SOURCE_REMOVE; }, - new IdleData{self, self->is_alive_}, + new IdleData{self, self->is_alive_, self->generation_}, [](gpointer data) { delete static_cast(data); }); } @@ -480,14 +519,14 @@ void AudioPlayer::OnInterrupted(player_interrupted_code_e code, void *data) { G_PRIORITY_DEFAULT_IDLE, [](gpointer data) -> gboolean { auto *idle = static_cast(data); - if (!*idle->is_alive) { + if (!*idle->is_alive || idle->generation != idle->player->generation_) { return G_SOURCE_REMOVE; } idle->player->log_listener_(idle->player->player_id_, "Player interrupted."); return G_SOURCE_REMOVE; }, - new IdleData{self, self->is_alive_}, + new IdleData{self, self->is_alive_, self->generation_}, [](gpointer data) { delete static_cast(data); }); } @@ -499,20 +538,23 @@ void AudioPlayer::OnError(int code, void *data) { struct ErrorData { AudioPlayer *player; std::shared_ptr is_alive; + unsigned int generation; std::string message; }; g_idle_add_full( G_PRIORITY_DEFAULT_IDLE, [](gpointer data) -> gboolean { auto *error_data = static_cast(data); - if (!*error_data->is_alive) { + if (!*error_data->is_alive || + error_data->generation != error_data->player->generation_) { return G_SOURCE_REMOVE; } error_data->player->log_listener_(error_data->player->player_id_, error_data->message); return G_SOURCE_REMOVE; }, - new ErrorData{self, self->is_alive_, get_error_message(code)}, + new ErrorData{self, self->is_alive_, self->generation_, + get_error_message(code)}, [](gpointer data) { delete static_cast(data); }); } diff --git a/packages/audioplayers/tizen/src/audio_player.h b/packages/audioplayers/tizen/src/audio_player.h index 9d634bede..1fc6c6afb 100644 --- a/packages/audioplayers/tizen/src/audio_player.h +++ b/packages/audioplayers/tizen/src/audio_player.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,7 @@ class AudioPlayer { bool IsSourcePrepared(); private: + enum class PendingAction { kNone, kPlay, kPause }; // The player state should be none before calling this function. void CreatePlayer(); // The player state should be idle before calling this function. @@ -86,6 +88,8 @@ class AudioPlayer { bool preparing_ = false; bool seeking_ = false; bool should_play_ = false; + PendingAction pending_action_ = PendingAction::kNone; + std::atomic generation_{0}; guint timer_id_ = 0; std::shared_ptr is_alive_ = std::make_shared(true); From 782f70528eaa976115879df9ddd40f296cf6cb3a Mon Sep 17 00:00:00 2001 From: Seungsoo Lee Date: Tue, 22 Sep 2026 13:31:42 +0900 Subject: [PATCH 2/5] [audioplayers] Handle pending completion callbacks --- packages/audioplayers/CHANGELOG.md | 1 + .../audioplayers/tizen/src/audio_player.cc | 94 ++++++++++++------- .../audioplayers/tizen/src/audio_player.h | 8 +- .../tizen/src/audioplayers_tizen_plugin.cc | 9 +- 4 files changed, 74 insertions(+), 38 deletions(-) diff --git a/packages/audioplayers/CHANGELOG.md b/packages/audioplayers/CHANGELOG.md index 0142186a1..8e8cad9c1 100644 --- a/packages/audioplayers/CHANGELOG.md +++ b/packages/audioplayers/CHANGELOG.md @@ -2,6 +2,7 @@ * Serialize seeks and defer playback controls until seeking completes. * Avoid missing network seek completion callbacks after stop by rewinding while paused. +* Reset the playback position before reporting completion in stop mode. ## 3.1.8 diff --git a/packages/audioplayers/tizen/src/audio_player.cc b/packages/audioplayers/tizen/src/audio_player.cc index 06f652dd6..46209ed8d 100644 --- a/packages/audioplayers/tizen/src/audio_player.cc +++ b/packages/audioplayers/tizen/src/audio_player.cc @@ -25,13 +25,14 @@ AudioPlayer::AudioPlayer(const std::string &player_id, DurationListener duration_listener, SeekCompletedListener seek_completed_listener, PlayCompletedListener play_completed_listener, - LogListener log_listener) + LogListener log_listener, ErrorListener error_listener) : player_id_(player_id), prepared_listener_(prepared_listener), duration_listener_(duration_listener), seek_completed_listener_(seek_completed_listener), play_completed_listener_(play_completed_listener), - log_listener_(log_listener) { + log_listener_(log_listener), + error_listener_(error_listener) { CreatePlayer(); } @@ -51,6 +52,7 @@ AudioPlayer::~AudioPlayer() { } void AudioPlayer::Play() { + completing_ = false; if (seeking_) { pending_action_ = PendingAction::kPlay; return; @@ -58,7 +60,7 @@ void AudioPlayer::Play() { player_state_e state = GetPlayerState(); if (state == PLAYER_STATE_IDLE && preparing_) { // Player is preparing, play will be called in prepared callback. - should_play_ = true; + pending_action_ = PendingAction::kPlay; return; } @@ -72,7 +74,7 @@ void AudioPlayer::Play() { throw AudioPlayerError("player_set_memory_buffer failed", get_error_message(ret)); } - should_play_ = true; + pending_action_ = PendingAction::kPlay; PreparePlayer(); } else if (url_.size() > 0) { int ret = player_set_uri(player_, url_.c_str()); @@ -80,7 +82,7 @@ void AudioPlayer::Play() { throw AudioPlayerError("player_set_uri failed", get_error_message(ret)); } - should_play_ = true; + pending_action_ = PendingAction::kPlay; PreparePlayer(); } break; @@ -91,7 +93,7 @@ void AudioPlayer::Play() { if (ret != PLAYER_ERROR_NONE) { throw AudioPlayerError("player_start failed", get_error_message(ret)); } - should_play_ = false; + pending_action_ = PendingAction::kNone; StartPositionUpdates(); break; } @@ -102,6 +104,7 @@ void AudioPlayer::Play() { } void AudioPlayer::Pause() { + completing_ = false; if (seeking_) { pending_action_ = PendingAction::kPause; return; @@ -113,11 +116,12 @@ void AudioPlayer::Pause() { } } - should_play_ = false; + pending_action_ = PendingAction::kNone; } void AudioPlayer::Stop() { - should_play_ = false; + completing_ = false; + pending_action_ = PendingAction::kNone; if (release_mode_ == ReleaseMode::kRelease) { ReleaseMediaSource(); return; @@ -148,6 +152,7 @@ void AudioPlayer::ReleaseMediaSource() { } void AudioPlayer::Seek(int32_t position) { + completing_ = false; if (seeking_) { should_seek_to_ = position; return; @@ -291,7 +296,7 @@ bool AudioPlayer::IsPlaying() { } void AudioPlayer::CreatePlayer() { - should_play_ = false; + pending_action_ = PendingAction::kNone; preparing_ = false; int ret = player_create(&player_); @@ -366,12 +371,16 @@ void AudioPlayer::ResetPlayer() { } break; } + bool seek_cancelled = seeking_ || should_seek_to_ >= 0; ++generation_; preparing_ = false; + completing_ = false; seeking_ = false; - should_play_ = false; should_seek_to_ = -1; pending_action_ = PendingAction::kNone; + if (seek_cancelled) { + seek_completed_listener_(player_id_); + } } player_state_e AudioPlayer::GetPlayerState() { @@ -399,6 +408,10 @@ void AudioPlayer::OnPrepared(void *data) { } auto *player = idle->player; player->preparing_ = false; + if (player->completing_) { + player->completing_ = false; + player->play_completed_listener_(player->player_id_); + } try { player->duration_listener_(player->player_id_, player->GetDuration()); @@ -410,33 +423,30 @@ void AudioPlayer::OnPrepared(void *data) { player_set_playback_rate(player->player_, player->playback_rate_); if (player->should_seek_to_ >= 0) { + int position = player->should_seek_to_; + player->should_seek_to_ = -1; player->seeking_ = true; - int ret = - player_set_play_position(player->player_, player->should_seek_to_, - true, OnSeekCompleted, player); + int ret = player_set_play_position(player->player_, position, true, + OnSeekCompleted, player); if (ret != PLAYER_ERROR_NONE) { player->seeking_ = false; - player->log_listener_(player->player_id_, - "player_set_play_position failed."); + player->error_listener_(player->player_id_, + "player_set_play_position failed", + get_error_message(ret)); + } else { return G_SOURCE_REMOVE; } - player->should_seek_to_ = -1; - if (player->should_play_) { - player->pending_action_ = PendingAction::kPlay; - player->should_play_ = false; - } - return G_SOURCE_REMOVE; } - if (player->should_play_) { + if (player->pending_action_ == PendingAction::kPlay) { int ret = player_start(player->player_); if (ret != PLAYER_ERROR_NONE) { player->log_listener_(player->player_id_, "player_start failed."); return G_SOURCE_REMOVE; } player->StartPositionUpdates(); - player->should_play_ = false; } + player->pending_action_ = PendingAction::kNone; return G_SOURCE_REMOVE; }, new IdleData{self, self->is_alive_, self->generation_}, @@ -457,15 +467,19 @@ void AudioPlayer::OnSeekCompleted(void *data) { } auto *player = idle->player; player->seeking_ = false; - try { - if (player->should_seek_to_ >= 0) { + if (player->should_seek_to_ >= 0) { + try { int position = player->should_seek_to_; player->should_seek_to_ = -1; player->Seek(position); return G_SOURCE_REMOVE; + } catch (const AudioPlayerError &error) { + player->OnLog(error.code() + ": " + error.message()); } - auto action = player->pending_action_; - player->pending_action_ = PendingAction::kNone; + } + auto action = player->pending_action_; + player->pending_action_ = PendingAction::kNone; + try { switch (action) { case PendingAction::kPlay: player->Play(); @@ -476,10 +490,10 @@ void AudioPlayer::OnSeekCompleted(void *data) { case PendingAction::kNone: break; } - player->seek_completed_listener_(player->player_id_); } catch (const AudioPlayerError &error) { player->OnLog(error.code() + ": " + error.message()); } + player->seek_completed_listener_(player->player_id_); return G_SOURCE_REMOVE; }, new IdleData{self, self->is_alive_, self->generation_}, @@ -500,9 +514,19 @@ void AudioPlayer::OnPlayCompleted(void *data) { } auto *player = idle->player; try { - player->Stop(); - player->play_completed_listener_(player->player_id_); + if (player->release_mode_ == ReleaseMode::kRelease) { + player->Stop(); + player->play_completed_listener_(player->player_id_); + } else { + player->ResetPlayer(); + player->completing_ = true; + player->PreparePlayer(); + } } catch (const AudioPlayerError &error) { + if (player->completing_) { + player->completing_ = false; + player->play_completed_listener_(player->player_id_); + } player->log_listener_(player->player_id_, error.code()); } return G_SOURCE_REMOVE; @@ -513,8 +537,6 @@ void AudioPlayer::OnPlayCompleted(void *data) { void AudioPlayer::OnInterrupted(player_interrupted_code_e code, void *data) { auto *self = reinterpret_cast(data); - // On TV devices, callbacks are not executed on the main loop. Transfer to - // the main loop so the log event is sent on the platform thread. g_idle_add_full( G_PRIORITY_DEFAULT_IDLE, [](gpointer data) -> gboolean { @@ -532,9 +554,6 @@ void AudioPlayer::OnInterrupted(player_interrupted_code_e code, void *data) { void AudioPlayer::OnError(int code, void *data) { auto *self = reinterpret_cast(data); - // On TV devices, callbacks are not executed on the main loop. Transfer to - // the main loop so the log event is sent on the platform thread. The error - // message is resolved here and carried via a heap-allocated context. struct ErrorData { AudioPlayer *player; std::shared_ptr is_alive; @@ -551,6 +570,11 @@ void AudioPlayer::OnError(int code, void *data) { } error_data->player->log_listener_(error_data->player->player_id_, error_data->message); + if (error_data->player->completing_) { + error_data->player->completing_ = false; + error_data->player->play_completed_listener_( + error_data->player->player_id_); + } return G_SOURCE_REMOVE; }, new ErrorData{self, self->is_alive_, self->generation_, diff --git a/packages/audioplayers/tizen/src/audio_player.h b/packages/audioplayers/tizen/src/audio_player.h index 1fc6c6afb..e2e5166fb 100644 --- a/packages/audioplayers/tizen/src/audio_player.h +++ b/packages/audioplayers/tizen/src/audio_player.h @@ -26,6 +26,9 @@ using SeekCompletedListener = std::function; using PlayCompletedListener = std::function; using LogListener = std::function; +using ErrorListener = + std::function; class AudioPlayer { public: @@ -33,7 +36,7 @@ class AudioPlayer { DurationListener duration_listener, SeekCompletedListener seek_completed_listener, PlayCompletedListener play_completed_listener, - LogListener log_listener); + LogListener log_listener, ErrorListener error_listener); ~AudioPlayer(); @@ -86,8 +89,8 @@ class AudioPlayer { ReleaseMode release_mode_ = ReleaseMode::kRelease; int should_seek_to_ = -1; bool preparing_ = false; + bool completing_ = false; bool seeking_ = false; - bool should_play_ = false; PendingAction pending_action_ = PendingAction::kNone; std::atomic generation_{0}; guint timer_id_ = 0; @@ -98,6 +101,7 @@ class AudioPlayer { SeekCompletedListener seek_completed_listener_; PlayCompletedListener play_completed_listener_; LogListener log_listener_; + ErrorListener error_listener_; }; #endif // FLUTTER_PLUGIN_AUDIO_PLAYER_H_ diff --git a/packages/audioplayers/tizen/src/audioplayers_tizen_plugin.cc b/packages/audioplayers/tizen/src/audioplayers_tizen_plugin.cc index f9723bf29..509cfe628 100644 --- a/packages/audioplayers/tizen/src/audioplayers_tizen_plugin.cc +++ b/packages/audioplayers/tizen/src/audioplayers_tizen_plugin.cc @@ -393,9 +393,16 @@ class AudioplayersTizenPlugin : public flutter::Plugin { event_sinks_[player_id]->Success(flutter::EncodableValue(map)); }; + ErrorListener error_listener = [this](const std::string &player_id, + const std::string &code, + const std::string &message) { + event_sinks_[player_id]->Error(code, message, flutter::EncodableValue()); + }; + auto player = std::make_unique( player_id, prepared_listener, duration_listener, - seek_completed_listener, play_completed_listener, log_listener); + seek_completed_listener, play_completed_listener, log_listener, + error_listener); audio_players_[player_id] = std::move(player); } From 6d7c6d19a3261719f49e95fbbb055555ebb20480 Mon Sep 17 00:00:00 2001 From: Seungsoo Lee Date: Tue, 22 Sep 2026 18:58:58 +0900 Subject: [PATCH 3/5] [audioplayers] Fix seek completion handling --- .../audioplayers/tizen/src/audio_player.cc | 56 ++++++++++++------- .../audioplayers/tizen/src/audio_player.h | 1 + 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/packages/audioplayers/tizen/src/audio_player.cc b/packages/audioplayers/tizen/src/audio_player.cc index 46209ed8d..511a918ac 100644 --- a/packages/audioplayers/tizen/src/audio_player.cc +++ b/packages/audioplayers/tizen/src/audio_player.cc @@ -67,23 +67,9 @@ void AudioPlayer::Play() { switch (state) { case PLAYER_STATE_NONE: case PLAYER_STATE_IDLE: { - if (audio_data_.size() > 0) { - int ret = player_set_memory_buffer(player_, audio_data_.data(), - audio_data_.size()); - if (ret != PLAYER_ERROR_NONE) { - throw AudioPlayerError("player_set_memory_buffer failed", - get_error_message(ret)); - } - pending_action_ = PendingAction::kPlay; - PreparePlayer(); - } else if (url_.size() > 0) { - int ret = player_set_uri(player_, url_.c_str()); - if (ret != PLAYER_ERROR_NONE) { - throw AudioPlayerError("player_set_uri failed", - get_error_message(ret)); - } + if (audio_data_.size() > 0 || url_.size() > 0) { pending_action_ = PendingAction::kPlay; - PreparePlayer(); + PrepareSource(); } break; } @@ -141,7 +127,7 @@ void AudioPlayer::Stop() { } } else if (state == PLAYER_STATE_READY) { ResetPlayer(); - PreparePlayer(); + PrepareSource(); } } @@ -152,7 +138,6 @@ void AudioPlayer::ReleaseMediaSource() { } void AudioPlayer::Seek(int32_t position) { - completing_ = false; if (seeking_) { should_seek_to_ = position; return; @@ -344,6 +329,26 @@ void AudioPlayer::PreparePlayer() { seeking_ = false; } +void AudioPlayer::PrepareSource() { + int ret; + if (audio_data_.size() > 0) { + ret = player_set_memory_buffer(player_, audio_data_.data(), + audio_data_.size()); + if (ret != PLAYER_ERROR_NONE) { + throw AudioPlayerError("player_set_memory_buffer failed", + get_error_message(ret)); + } + } else if (url_.size() > 0) { + ret = player_set_uri(player_, url_.c_str()); + if (ret != PLAYER_ERROR_NONE) { + throw AudioPlayerError("player_set_uri failed", get_error_message(ret)); + } + } else { + return; + } + PreparePlayer(); +} + void AudioPlayer::ResetPlayer() { player_state_e state = GetPlayerState(); switch (state) { @@ -467,6 +472,14 @@ void AudioPlayer::OnSeekCompleted(void *data) { } auto *player = idle->player; player->seeking_ = false; + if (player->pending_action_ == PendingAction::kPause) { + player->pending_action_ = PendingAction::kNone; + try { + player->Pause(); + } catch (const AudioPlayerError &error) { + player->OnLog(error.code() + ": " + error.message()); + } + } if (player->should_seek_to_ >= 0) { try { int position = player->should_seek_to_; @@ -474,7 +487,9 @@ void AudioPlayer::OnSeekCompleted(void *data) { player->Seek(position); return G_SOURCE_REMOVE; } catch (const AudioPlayerError &error) { - player->OnLog(error.code() + ": " + error.message()); + player->error_listener_(player->player_id_, error.code(), + error.message()); + return G_SOURCE_REMOVE; } } auto action = player->pending_action_; @@ -485,7 +500,6 @@ void AudioPlayer::OnSeekCompleted(void *data) { player->Play(); break; case PendingAction::kPause: - player->Pause(); break; case PendingAction::kNone: break; @@ -520,7 +534,7 @@ void AudioPlayer::OnPlayCompleted(void *data) { } else { player->ResetPlayer(); player->completing_ = true; - player->PreparePlayer(); + player->PrepareSource(); } } catch (const AudioPlayerError &error) { if (player->completing_) { diff --git a/packages/audioplayers/tizen/src/audio_player.h b/packages/audioplayers/tizen/src/audio_player.h index e2e5166fb..400856a63 100644 --- a/packages/audioplayers/tizen/src/audio_player.h +++ b/packages/audioplayers/tizen/src/audio_player.h @@ -67,6 +67,7 @@ class AudioPlayer { void CreatePlayer(); // The player state should be idle before calling this function. void PreparePlayer(); + void PrepareSource(); void ResetPlayer(); void StartPositionUpdates(); player_state_e GetPlayerState(); From 77f79e287cb3fdc69356b84fec9ab63e4f2d4bb5 Mon Sep 17 00:00:00 2001 From: Seungsoo Lee Date: Wed, 30 Sep 2026 19:25:39 +0900 Subject: [PATCH 4/5] [audioplayers] Keep pending completion across playback controls - Do not discard a pending completion on pause. - Report a pending completion before play, stop and source reset. - Report completion even if re-preparing fails after playback ends. - Run the deferred play when a chained seek fails. --- packages/audioplayers/CHANGELOG.md | 1 + .../audioplayers/tizen/src/audio_player.cc | 44 +++++++------------ .../audioplayers/tizen/src/audio_player.h | 2 + 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/packages/audioplayers/CHANGELOG.md b/packages/audioplayers/CHANGELOG.md index 8e8cad9c1..25ee301d6 100644 --- a/packages/audioplayers/CHANGELOG.md +++ b/packages/audioplayers/CHANGELOG.md @@ -3,6 +3,7 @@ * Serialize seeks and defer playback controls until seeking completes. * Avoid missing network seek completion callbacks after stop by rewinding while paused. * Reset the playback position before reporting completion in stop mode. +* Report deferred seek failures as errors. ## 3.1.8 diff --git a/packages/audioplayers/tizen/src/audio_player.cc b/packages/audioplayers/tizen/src/audio_player.cc index 511a918ac..83c44be92 100644 --- a/packages/audioplayers/tizen/src/audio_player.cc +++ b/packages/audioplayers/tizen/src/audio_player.cc @@ -52,7 +52,7 @@ AudioPlayer::~AudioPlayer() { } void AudioPlayer::Play() { - completing_ = false; + NotifyPendingCompletion(); if (seeking_) { pending_action_ = PendingAction::kPlay; return; @@ -90,7 +90,6 @@ void AudioPlayer::Play() { } void AudioPlayer::Pause() { - completing_ = false; if (seeking_) { pending_action_ = PendingAction::kPause; return; @@ -106,7 +105,7 @@ void AudioPlayer::Pause() { } void AudioPlayer::Stop() { - completing_ = false; + NotifyPendingCompletion(); pending_action_ = PendingAction::kNone; if (release_mode_ == ReleaseMode::kRelease) { ReleaseMediaSource(); @@ -214,7 +213,7 @@ void AudioPlayer::SetVolume(double volume) { void AudioPlayer::SetPlaybackRate(double playback_rate) { // TODO(seungsoo47): The player_set_playback_rate() API has a limitation of - // 0.5-2x on TV and is not supported on RPI. + // 0.5-2x on TV. On RPI, it supports 0.5-5x for local files only. playback_rate_ = playback_rate; player_state_e state = GetPlayerState(); if (state == PLAYER_STATE_READY || state == PLAYER_STATE_PLAYING || @@ -379,7 +378,7 @@ void AudioPlayer::ResetPlayer() { bool seek_cancelled = seeking_ || should_seek_to_ >= 0; ++generation_; preparing_ = false; - completing_ = false; + NotifyPendingCompletion(); seeking_ = false; should_seek_to_ = -1; pending_action_ = PendingAction::kNone; @@ -388,6 +387,13 @@ void AudioPlayer::ResetPlayer() { } } +void AudioPlayer::NotifyPendingCompletion() { + if (completing_) { + completing_ = false; + play_completed_listener_(player_id_); + } +} + player_state_e AudioPlayer::GetPlayerState() { player_state_e state = PLAYER_STATE_NONE; if (player_) { @@ -413,10 +419,7 @@ void AudioPlayer::OnPrepared(void *data) { } auto *player = idle->player; player->preparing_ = false; - if (player->completing_) { - player->completing_ = false; - player->play_completed_listener_(player->player_id_); - } + player->NotifyPendingCompletion(); try { player->duration_listener_(player->player_id_, player->GetDuration()); @@ -489,20 +492,13 @@ void AudioPlayer::OnSeekCompleted(void *data) { } catch (const AudioPlayerError &error) { player->error_listener_(player->player_id_, error.code(), error.message()); - return G_SOURCE_REMOVE; } } auto action = player->pending_action_; player->pending_action_ = PendingAction::kNone; try { - switch (action) { - case PendingAction::kPlay: - player->Play(); - break; - case PendingAction::kPause: - break; - case PendingAction::kNone: - break; + if (action == PendingAction::kPlay) { + player->Play(); } } catch (const AudioPlayerError &error) { player->OnLog(error.code() + ": " + error.message()); @@ -537,10 +533,8 @@ void AudioPlayer::OnPlayCompleted(void *data) { player->PrepareSource(); } } catch (const AudioPlayerError &error) { - if (player->completing_) { - player->completing_ = false; - player->play_completed_listener_(player->player_id_); - } + player->completing_ = false; + player->play_completed_listener_(player->player_id_); player->log_listener_(player->player_id_, error.code()); } return G_SOURCE_REMOVE; @@ -584,11 +578,7 @@ void AudioPlayer::OnError(int code, void *data) { } error_data->player->log_listener_(error_data->player->player_id_, error_data->message); - if (error_data->player->completing_) { - error_data->player->completing_ = false; - error_data->player->play_completed_listener_( - error_data->player->player_id_); - } + error_data->player->NotifyPendingCompletion(); return G_SOURCE_REMOVE; }, new ErrorData{self, self->is_alive_, self->generation_, diff --git a/packages/audioplayers/tizen/src/audio_player.h b/packages/audioplayers/tizen/src/audio_player.h index 400856a63..e911d8df8 100644 --- a/packages/audioplayers/tizen/src/audio_player.h +++ b/packages/audioplayers/tizen/src/audio_player.h @@ -69,6 +69,8 @@ class AudioPlayer { void PreparePlayer(); void PrepareSource(); void ResetPlayer(); + // Reports a completion deferred while re-preparing in stop mode. + void NotifyPendingCompletion(); void StartPositionUpdates(); player_state_e GetPlayerState(); From 42fc47abe51a539a76500893090297c12cea3005 Mon Sep 17 00:00:00 2001 From: Seungsoo Lee Date: Fri, 2 Oct 2026 12:55:33 +0900 Subject: [PATCH 5/5] [audioplayers] Skip seek completion after a failed chained seek --- packages/audioplayers/tizen/src/audio_player.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/audioplayers/tizen/src/audio_player.cc b/packages/audioplayers/tizen/src/audio_player.cc index 83c44be92..cfc7ff5c1 100644 --- a/packages/audioplayers/tizen/src/audio_player.cc +++ b/packages/audioplayers/tizen/src/audio_player.cc @@ -483,6 +483,7 @@ void AudioPlayer::OnSeekCompleted(void *data) { player->OnLog(error.code() + ": " + error.message()); } } + bool seek_failed = false; if (player->should_seek_to_ >= 0) { try { int position = player->should_seek_to_; @@ -490,6 +491,7 @@ void AudioPlayer::OnSeekCompleted(void *data) { player->Seek(position); return G_SOURCE_REMOVE; } catch (const AudioPlayerError &error) { + seek_failed = true; player->error_listener_(player->player_id_, error.code(), error.message()); } @@ -503,7 +505,9 @@ void AudioPlayer::OnSeekCompleted(void *data) { } catch (const AudioPlayerError &error) { player->OnLog(error.code() + ": " + error.message()); } - player->seek_completed_listener_(player->player_id_); + if (!seek_failed) { + player->seek_completed_listener_(player->player_id_); + } return G_SOURCE_REMOVE; }, new IdleData{self, self->is_alive_, self->generation_},