Skip to content

[audioplayers] Fix resource cleanup bugs #871

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/audioplayers/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
## NEXT
## 3.1.2

* Update code format.
* Handle player resource cleanup in destructor
* Rename Release() to ReleaseMediaSource() for clearer responsibility
* Handle sending null values when exceptions occur in GetDuration() and GetPosition()
* Add logging for unsupported features
* Fix range limitation of SetVolume() and SetPlaybackRate()


## 3.1.1

Expand Down
3 changes: 2 additions & 1 deletion packages/audioplayers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This package is not an _endorsed_ implementation of `audioplayers`. Therefore, y
```yaml
dependencies:
audioplayers: ^6.4.0
audioplayers_tizen: ^3.1.1
audioplayers_tizen: ^3.1.2

```

Expand Down Expand Up @@ -70,3 +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.
2 changes: 2 additions & 0 deletions packages/audioplayers/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const defaultPlayerCount = 4;

typedef OnError = void Function(Exception exception);

// ignore_for_file: unnecessary_breaks

/// The app is deployed at: https://bluefireteam.github.io/audioplayers/
void main() {
runApp(const MaterialApp(home: _ExampleApp()));
Expand Down
2 changes: 1 addition & 1 deletion packages/audioplayers/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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/master/packages/audioplayers
version: 3.1.1
version: 3.1.2

environment:
sdk: ">=3.1.0 <4.0.0"
Expand Down
71 changes: 38 additions & 33 deletions packages/audioplayers/tizen/src/audio_player.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,19 @@ AudioPlayer::AudioPlayer(const std::string &player_id,
CreatePlayer();
}

AudioPlayer::~AudioPlayer() { Release(); }
AudioPlayer::~AudioPlayer() {
if (player_) {
player_unset_completed_cb(player_);
player_unset_interrupted_cb(player_);
player_unset_error_cb(player_);
player_destroy(player_);
player_ = nullptr;
}
if (timer_) {
ecore_timer_del(timer_);
timer_ = nullptr;
}
}

void AudioPlayer::Play() {
player_state_e state = GetPlayerState();
Expand All @@ -42,15 +54,17 @@ void AudioPlayer::Play() {
throw AudioPlayerError("player_set_memory_buffer failed",
get_error_message(ret));
}
} else {
should_play_ = true;
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));
}
should_play_ = true;
PreparePlayer();
}
should_play_ = true;
PreparePlayer();
break;
}
case PLAYER_STATE_READY:
Expand Down Expand Up @@ -93,23 +107,14 @@ void AudioPlayer::Stop() {
seeking_ = false;

if (release_mode_ == ReleaseMode::kRelease) {
Release();
ReleaseMediaSource();
}
}

void AudioPlayer::Release() {
if (player_) {
player_unset_completed_cb(player_);
player_unset_interrupted_cb(player_);
player_unset_error_cb(player_);
player_destroy(player_);
player_ = nullptr;
}
if (timer_) {
ecore_timer_del(timer_);
timer_ = nullptr;
}
released_ = true;
void AudioPlayer::ReleaseMediaSource() {
url_.clear();
audio_data_.clear();
ResetPlayer();
}

void AudioPlayer::Seek(int32_t position) {
Expand All @@ -134,6 +139,12 @@ void AudioPlayer::Seek(int32_t position) {
}
}

void AudioPlayer::OnLog(const std::string &message) {
if (log_listener_) {
log_listener_(player_id_, message);
}
}

void AudioPlayer::SetUrl(const std::string &url) {
url_ = url;
ResetPlayer();
Expand Down Expand Up @@ -164,7 +175,13 @@ void AudioPlayer::SetDataSource(std::vector<uint8_t> &data) {
}

void AudioPlayer::SetVolume(double volume) {
if (volume > 1) {
volume = 1;
} else if (volume < 0) {
volume = 0;
}
volume_ = volume;

if (GetPlayerState() != PLAYER_STATE_NONE) {
int ret = player_set_volume(player_, volume_, volume_);
if (ret != PLAYER_ERROR_NONE) {
Expand All @@ -175,6 +192,8 @@ 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.
playback_rate_ = playback_rate;
player_state_e state = GetPlayerState();
if (state == PLAYER_STATE_READY || state == PLAYER_STATE_PLAYING ||
Expand Down Expand Up @@ -221,16 +240,6 @@ int AudioPlayer::GetDuration() {
}

int AudioPlayer::GetCurrentPosition() {
// TODO(jsuya) : When stop() or pause() is called in AudioPlayer 6.1.0,
// PositionUpdater's stopAndUpdate() is called. At this time, getPosition() is
// called, but in ReleaseMode, the player is released after Stop(), so an
// exception is thrown. Since there are differences from the implementation in
// the frontend package, an exception is not thrown in this case.
if (!player_ && released_ && release_mode_ == ReleaseMode::kRelease) {
LOG_ERROR("The player has already been released.");
return 0;
}

int32_t position;
int ret = player_get_play_position(player_, &position);
if (ret != PLAYER_ERROR_NONE) {
Expand Down Expand Up @@ -270,8 +279,6 @@ void AudioPlayer::CreatePlayer() {
throw AudioPlayerError("player_set_error_cb failed",
get_error_message(ret));
}

released_ = false;
}

void AudioPlayer::PreparePlayer() {
Expand Down Expand Up @@ -441,9 +448,7 @@ Eina_Bool AudioPlayer::OnPositionUpdate(void *data) {
auto *player = reinterpret_cast<AudioPlayer *>(data);
try {
if (player->IsPlaying()) {
int32_t duration = player->GetDuration();
player->duration_listener_(player->player_id_, duration);

player->duration_listener_(player->player_id_, player->GetDuration());
return ECORE_CALLBACK_RENEW;
}
} catch (const AudioPlayerError &error) {
Expand Down
4 changes: 2 additions & 2 deletions packages/audioplayers/tizen/src/audio_player.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ class AudioPlayer {
void Play();
void Pause();
void Stop();
void Release();
void ReleaseMediaSource();
void Seek(int32_t position); // milliseconds
void OnLog(const std::string &message);

// If you use HTTP or RTSP, URI must start with "http://" or "rtsp://".
// The default protocol is "file://".
Expand Down Expand Up @@ -83,7 +84,6 @@ class AudioPlayer {
bool preparing_ = false;
bool seeking_ = false;
bool should_play_ = false;
bool released_ = false;
Ecore_Timer *timer_ = nullptr;

PreparedListener prepared_listener_;
Expand Down
53 changes: 38 additions & 15 deletions packages/audioplayers/tizen/src/audioplayers_tizen_plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,17 @@ class AudioplayersTizenPlugin : public flutter::Plugin {
player->Stop();
result->Success();
} else if (method_name == "release") {
player->Release();
player->ReleaseMediaSource();
result->Success();
} else if (method_name == "seek") {
player->Seek(GetRequiredArg<int32_t>(arguments, "position"));
result->Success();
} else if (method_name == "setVolume") {
player->SetVolume(GetRequiredArg<double>(arguments, "volume"));
result->Success();
} else if (method_name == "setBalance") {
player->OnLog("SetBalance() is not supported on Tizen");
result->NotImplemented();
} else if (method_name == "setSourceUrl") {
bool is_local = false;
GetValueFromEncodableMap(arguments, "isLocal", is_local);
Expand All @@ -221,15 +224,35 @@ class AudioplayersTizenPlugin : public flutter::Plugin {
player->SetReleaseMode(StringToReleaseMode(release_mode));
result->Success();
} else if (method_name == "getDuration") {
result->Success(flutter::EncodableValue(player->GetDuration()));
// TODO(seungsoo47): If an exception occurs, null is sent.
try {
result->Success(flutter::EncodableValue(player->GetDuration()));
} catch (const AudioPlayerError &error) {
player->OnLog(error.code() + error.message());
result->Success(flutter::EncodableValue(std::monostate()));
}
} else if (method_name == "getCurrentPosition") {
result->Success(flutter::EncodableValue(player->GetCurrentPosition()));
// TODO(seungsoo47): If an exception occurs, null is sent.
try {
result->Success(
flutter::EncodableValue(player->GetCurrentPosition()));
} catch (const AudioPlayerError &error) {
player->OnLog(error.code() + error.message());
result->Success(flutter::EncodableValue(std::monostate()));
}
} else if (method_name == "setPlayerMode") {
bool low_latency =
GetRequiredArg<std::string>(arguments, "playerMode") ==
"PlayerMode.lowLatency";
player->SetLatencyMode(low_latency);
result->Success();
} else if (method_name == "setAudioContext") {
player->OnLog("Setting AudioContext is not supported on Tizen");
result->NotImplemented();
} else if (method_name == "emitLog") {
auto message = GetRequiredArg<std::string>(arguments, "message");
player->OnLog(message);
result->Success();
} else {
result->NotImplemented();
}
Expand All @@ -252,21 +275,13 @@ class AudioplayersTizenPlugin : public flutter::Plugin {
}
audio_players_.clear();
} else if (method_name == "setAudioContext") {
const std::string message =
"Setting AudioContext is not supported on Tizen";
flutter::EncodableMap map = {{flutter::EncodableValue("event"),
flutter::EncodableValue("audio.onLog")},
{flutter::EncodableValue("value"),
flutter::EncodableValue(message)}};
global_event_sinks_->Success(flutter::EncodableValue(map));
OnGlobalLog("Setting AudioContext is not supported on Tizen");
result->NotImplemented();
return;
} else if (method_name == "emitLog") {
if (arguments) {
auto message = GetRequiredArg<std::string>(arguments, "message");
flutter::EncodableMap map = {{flutter::EncodableValue("event"),
flutter::EncodableValue("audio.onLog")},
{flutter::EncodableValue("value"),
flutter::EncodableValue(message)}};
global_event_sinks_->Success(flutter::EncodableValue(map));
OnGlobalLog(message);
}
} else if (method_name == "emitError") {
if (arguments) {
Expand Down Expand Up @@ -359,6 +374,14 @@ class AudioplayersTizenPlugin : public flutter::Plugin {
event_sinks_.erase(player_id);
}

void OnGlobalLog(const std::string &message) {
flutter::EncodableMap map = {
{flutter::EncodableValue("event"),
flutter::EncodableValue("audio.onLog")},
{flutter::EncodableValue("value"), flutter::EncodableValue(message)}};
global_event_sinks_->Success(flutter::EncodableValue(map));
}

std::map<std::string, std::unique_ptr<AudioPlayer>> audio_players_;
std::map<std::string, std::unique_ptr<FlEventSink>> event_sinks_;
std::unique_ptr<FlEventSink> global_event_sinks_;
Expand Down