diff --git a/packages/video_player_avplay/CHANGELOG.md b/packages/video_player_avplay/CHANGELOG.md index 377203b7d..09f9a2f34 100644 --- a/packages/video_player_avplay/CHANGELOG.md +++ b/packages/video_player_avplay/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Handle consecutive seekTo calls. + ## 0.8.18 * Replace ecore-wl2 code with tizen window manager plugin. diff --git a/packages/video_player_avplay/lib/src/video_player_tizen.dart b/packages/video_player_avplay/lib/src/video_player_tizen.dart index 5df865ef4..3a9e6491a 100644 --- a/packages/video_player_avplay/lib/src/video_player_tizen.dart +++ b/packages/video_player_avplay/lib/src/video_player_tizen.dart @@ -3,6 +3,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:tizen_window_manager/tizen_window_manager.dart'; @@ -11,11 +13,23 @@ import '../video_player_platform_interface.dart'; import 'messages.g.dart'; import 'tracks.dart'; +class _SeekOperation { + _SeekOperation(this.position); + + final int position; + final Completer completer = Completer(); +} + /// An implementation of [VideoPlayerPlatform] that uses the /// Pigeon-generated [VideoPlayerAvplayApi]. class VideoPlayerTizen extends VideoPlayerPlatform { final VideoPlayerAvplayApi _api = VideoPlayerAvplayApi(); + final Map _activeSeeks = {}; + + final Map> _pendingSeeks = + >{}; + @override Future init() { return _api.initialize(); @@ -23,6 +37,7 @@ class VideoPlayerTizen extends VideoPlayerPlatform { @override Future dispose(int playerId) { + _cancelAllSeeks(playerId); return _api.dispose(PlayerMessage(playerId: playerId)); } @@ -116,9 +131,85 @@ class VideoPlayerTizen extends VideoPlayerPlatform { @override Future seekTo(int playerId, Duration position) { - return _api.seekTo( - PositionMessage(playerId: playerId, position: position.inMilliseconds), - ); + final int targetPosition = position.inMilliseconds; + + if (_activeSeeks.containsKey(playerId)) { + final _SeekOperation op = _SeekOperation(targetPosition); + _pendingSeeks.putIfAbsent(playerId, () => <_SeekOperation>[]); + _pendingSeeks[playerId]!.add(op); + return op.completer.future; + } + + return _startSeek(playerId, targetPosition); + } + + Future _startSeek(int playerId, int position) async { + final _SeekOperation op = _SeekOperation(position); + _activeSeeks[playerId] = op; + + try { + await _api.seekTo( + PositionMessage(playerId: playerId, position: position), + ); + } catch (e) { + _completeSeekWithError(playerId, e); + } + } + + void _handleSeekCompleted(int playerId) { + final _SeekOperation? op = _activeSeeks.remove(playerId); + if (op != null && !op.completer.isCompleted) { + op.completer.complete(); + } + _startPendingSeekIfAny(playerId); + } + + void _startPendingSeekIfAny(int playerId) { + final List<_SeekOperation>? pending = _pendingSeeks[playerId]; + if (pending != null && pending.isNotEmpty) { + final _SeekOperation next = pending.removeAt(0); + _startSeek(playerId, next.position).then((_) { + if (!next.completer.isCompleted) { + next.completer.complete(); + } + }).catchError((Object e) { + if (!next.completer.isCompleted) { + next.completer.completeError(e); + } + }); + } else { + _pendingSeeks.remove(playerId); + } + } + + void _completeSeekWithError(int playerId, Object error) { + final _SeekOperation? op = _activeSeeks.remove(playerId); + if (op != null && !op.completer.isCompleted) { + op.completer.completeError(error); + } + final List<_SeekOperation>? pending = _pendingSeeks.remove(playerId); + if (pending != null) { + for (final _SeekOperation p in pending) { + if (!p.completer.isCompleted) { + p.completer.completeError(error); + } + } + } + } + + void _cancelAllSeeks(int playerId) { + final _SeekOperation? op = _activeSeeks.remove(playerId); + if (op != null && !op.completer.isCompleted) { + op.completer.completeError('Player was disposed.'); + } + final List<_SeekOperation>? pending = _pendingSeeks.remove(playerId); + if (pending != null) { + for (final _SeekOperation p in pending) { + if (!p.completer.isCompleted) { + p.completer.completeError('Player was disposed.'); + } + } + } } @override @@ -443,7 +534,6 @@ class VideoPlayerTizen extends VideoPlayerPlatform { return VideoEvent(eventType: VideoEventType.completed); case 'bufferingUpdate': final int value = map['value']! as int; - return VideoEvent( buffered: value, eventType: VideoEventType.bufferingUpdate, @@ -475,6 +565,9 @@ class VideoPlayerTizen extends VideoPlayerPlatform { eventType: VideoEventType.manifestInfoUpdated, manifestInfo: map['manifestInfo'] as String?, ); + case 'seekCompleted': + _handleSeekCompleted(playerId); + return VideoEvent(eventType: VideoEventType.unknown); default: return VideoEvent(eventType: VideoEventType.unknown); } diff --git a/packages/video_player_avplay/tizen/src/media_player.cc b/packages/video_player_avplay/tizen/src/media_player.cc index bd11f7536..dac28e084 100644 --- a/packages/video_player_avplay/tizen/src/media_player.cc +++ b/packages/video_player_avplay/tizen/src/media_player.cc @@ -231,7 +231,7 @@ bool MediaPlayer::Play() { } if (state == PLAYER_STATE_PLAYING) { LOG_INFO("[MediaPlayer] Player already playing."); - return false; + return true; } ret = player_start(player_); if (ret != PLAYER_ERROR_NONE) { @@ -256,7 +256,7 @@ bool MediaPlayer::Pause() { } if (state != PLAYER_STATE_PLAYING) { LOG_INFO("[MediaPlayer] Player not playing."); - return false; + return true; } ret = player_pause(player_); if (ret != PLAYER_ERROR_NONE) { @@ -306,11 +306,18 @@ bool MediaPlayer::SetPlaybackSpeed(double speed) { bool MediaPlayer::SeekTo(int64_t position, SeekCompletedCallback callback) { LOG_INFO("[MediaPlayer] position: %lld.", position); + if (is_seeking_) { + LOG_ERROR("[MediaPlayer] Seek is already in progress."); + return false; + } + on_seek_completed_ = std::move(callback); + is_seeking_ = true; int ret = player_set_play_position(player_, position, true, OnSeekCompleted, this); if (ret != PLAYER_ERROR_NONE) { on_seek_completed_ = nullptr; + is_seeking_ = false; LOG_ERROR("[MediaPlayer] player_set_play_position failed: %s.", get_error_message(ret)); return false; @@ -705,10 +712,12 @@ void MediaPlayer::OnSeekCompleted(void *user_data) { LOG_INFO("[MediaPlayer] Seek completed."); MediaPlayer *self = static_cast(user_data); + self->is_seeking_ = false; if (self->on_seek_completed_) { self->on_seek_completed_(); self->on_seek_completed_ = nullptr; } + self->SendSeekCompleted(); } void MediaPlayer::OnPlayCompleted(void *user_data) { @@ -809,36 +818,40 @@ bool MediaPlayer::StopAndDestroy() { return false; } + bool success = true; is_buffering_ = false; + on_seek_completed_ = nullptr; + is_seeking_ = false; player_state_e player_state = PLAYER_STATE_NONE; int ret = player_get_state(player_, &player_state); if (ret != PLAYER_ERROR_NONE) { LOG_ERROR("[MediaPlayer] player_get_state failed: %s.", get_error_message(ret)); - return false; - } - if (player_state == PLAYER_STATE_NONE || player_state == PLAYER_STATE_IDLE) { - LOG_INFO("[MediaPlayer] Player already stop, nothing to do."); - return true; + success = false; } - if (player_stop(player_) != PLAYER_ERROR_NONE) { - LOG_ERROR("[MediaPlayer] Player fail to stop."); - return false; + if (player_state == PLAYER_STATE_PLAYING || + player_state == PLAYER_STATE_PAUSED) { + if (player_stop(player_) != PLAYER_ERROR_NONE) { + LOG_ERROR("[MediaPlayer] Player fail to stop."); + success = false; + } } - if (player_unprepare(player_) != PLAYER_ERROR_NONE) { - LOG_ERROR("[MediaPlayer] Player fail to unprepare."); - return false; + if (player_state != PLAYER_STATE_NONE && player_state != PLAYER_STATE_IDLE) { + if (player_unprepare(player_) != PLAYER_ERROR_NONE) { + LOG_ERROR("[MediaPlayer] Player fail to unprepare."); + success = false; + } } if (player_destroy(player_) != PLAYER_ERROR_NONE) { LOG_ERROR("[MediaPlayer] Player fail to destroy."); - return false; + success = false; } player_ = nullptr; - return true; + return success; } bool MediaPlayer::Suspend() { diff --git a/packages/video_player_avplay/tizen/src/media_player.h b/packages/video_player_avplay/tizen/src/media_player.h index a7d4a71d6..2fa58d68b 100644 --- a/packages/video_player_avplay/tizen/src/media_player.h +++ b/packages/video_player_avplay/tizen/src/media_player.h @@ -78,6 +78,7 @@ class MediaPlayer : public VideoPlayer { std::unique_ptr drm_manager_; bool is_buffering_ = false; SeekCompletedCallback on_seek_completed_; + bool is_seeking_ = false; std::string url_; player_state_e pre_state_; int64_t pre_playing_time_; diff --git a/packages/video_player_avplay/tizen/src/plus_player.cc b/packages/video_player_avplay/tizen/src/plus_player.cc index 43be1db01..b4d67b692 100644 --- a/packages/video_player_avplay/tizen/src/plus_player.cc +++ b/packages/video_player_avplay/tizen/src/plus_player.cc @@ -194,6 +194,8 @@ int64_t PlusPlayer::Create(const std::string &uri, void PlusPlayer::Dispose() { LOG_INFO("[PlusPlayer] Player disposing."); + on_seek_completed_ = nullptr; + is_seeking_ = false; ClearUpEventChannel(); } @@ -231,7 +233,7 @@ bool PlusPlayer::Play() { } return true; } - return false; + return true; } bool PlusPlayer::Activate() { @@ -285,7 +287,7 @@ bool PlusPlayer::Pause() { if (state != plusplayer::State::kPlaying) { LOG_INFO("[PlusPlayer] Player not playing."); - return false; + return true; } if (!::Pause(player_)) { @@ -339,14 +341,16 @@ bool PlusPlayer::SeekTo(int64_t position, SeekCompletedCallback callback) { return false; } - if (on_seek_completed_) { + if (is_seeking_) { LOG_ERROR("[PlusPlayer] Player is already seeking."); return false; } on_seek_completed_ = std::move(callback); + is_seeking_ = true; if (!Seek(player_, position)) { on_seek_completed_ = nullptr; + is_seeking_ = false; LOG_ERROR("[PlusPlayer] Player fail to seek."); return false; } @@ -818,25 +822,29 @@ bool PlusPlayer::StopAndClose() { return false; } + bool success = true; is_buffering_ = false; - plusplayer::State player_state = GetState(player_); - if (player_state < plusplayer::State::kReady) { - LOG_INFO("[PlusPlayer] Player already stop, nothing to do."); - return true; + if (is_seeking_) { + SendSeekCompleted(); } + on_seek_completed_ = nullptr; + is_seeking_ = false; + plusplayer::State player_state = GetState(player_); if (drm_manager_) { drm_manager_->StopDrmSession(); } - if (!::Stop(player_)) { - LOG_ERROR("[PlusPlayer] Player fail to stop."); - return false; - } + if (player_state != plusplayer::State::kNone) { + if (!::Stop(player_)) { + LOG_ERROR("[PlusPlayer] Player fail to stop."); + success = false; + } - if (!::Close(player_)) { - LOG_ERROR("[PlusPlayer] Player fail to close."); - return false; + if (!::Close(player_)) { + LOG_ERROR("[PlusPlayer] Player fail to close."); + success = false; + } } if (drm_manager_) { @@ -844,7 +852,7 @@ bool PlusPlayer::StopAndClose() { drm_manager_.reset(); } - return true; + return success; } bool PlusPlayer::Suspend() { @@ -1173,10 +1181,12 @@ void PlusPlayer::OnSeekDone(void *user_data) { LOG_INFO("[PlusPlayer] Seek completed."); PlusPlayer *self = reinterpret_cast(user_data); + self->is_seeking_ = false; if (self->on_seek_completed_) { self->on_seek_completed_(); self->on_seek_completed_ = nullptr; } + self->SendSeekCompleted(); } void PlusPlayer::OnEos(void *user_data) { diff --git a/packages/video_player_avplay/tizen/src/plus_player.h b/packages/video_player_avplay/tizen/src/plus_player.h index a510c1334..987a2e87d 100644 --- a/packages/video_player_avplay/tizen/src/plus_player.h +++ b/packages/video_player_avplay/tizen/src/plus_player.h @@ -109,6 +109,7 @@ class PlusPlayer : public VideoPlayer { bool is_buffering_ = false; bool is_prebuffer_mode_ = false; SeekCompletedCallback on_seek_completed_; + bool is_seeking_ = false; std::unique_ptr memento_ = nullptr; std::string url_; std::unique_ptr device_proxy_ = nullptr; diff --git a/packages/video_player_avplay/tizen/src/video_player.cc b/packages/video_player_avplay/tizen/src/video_player.cc index de6cbec1e..b641be380 100644 --- a/packages/video_player_avplay/tizen/src/video_player.cc +++ b/packages/video_player_avplay/tizen/src/video_player.cc @@ -145,6 +145,14 @@ void VideoPlayer::SendBufferingEnd() { PushEvent(flutter::EncodableValue(result)); } +void VideoPlayer::SendSeekCompleted() { + flutter::EncodableMap result = { + {flutter::EncodableValue("event"), + flutter::EncodableValue("seekCompleted")}, + }; + PushEvent(flutter::EncodableValue(result)); +} + void VideoPlayer::SendSubtitleUpdate(int32_t duration, flutter::EncodableList texts_info, flutter::EncodableMap picture_info) { diff --git a/packages/video_player_avplay/tizen/src/video_player.h b/packages/video_player_avplay/tizen/src/video_player.h index a9a89308e..6341d329f 100644 --- a/packages/video_player_avplay/tizen/src/video_player.h +++ b/packages/video_player_avplay/tizen/src/video_player.h @@ -81,6 +81,7 @@ class VideoPlayer { void SendBufferingStart(); void SendBufferingUpdate(int32_t value); void SendBufferingEnd(); + void SendSeekCompleted(); void SendSubtitleUpdate( int32_t duration, flutter::EncodableList texts_info, flutter::EncodableMap picture_info = flutter::EncodableMap{});