Skip to content
Draft
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
770 changes: 770 additions & 0 deletions packages/video_player_videohole/lib/src/ffi_messages.g.dart

Large diffs are not rendered by default.

985 changes: 0 additions & 985 deletions packages/video_player_videohole/lib/src/messages.g.dart

This file was deleted.

419 changes: 279 additions & 140 deletions packages/video_player_videohole/lib/src/video_player_tizen.dart

Large diffs are not rendered by default.

42 changes: 36 additions & 6 deletions packages/video_player_videohole/lib/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
RestoreDataSourceCallback? _onRestoreDataSource;
RestoreTimeCallback? _onRestoreTime;

// Store event and error listeners for re-subscription during restore
void Function(VideoEvent)? _eventListener;
void Function(Object)? _errorListener;

/// The id of a player that hasn't been initialized.
@visibleForTesting
static const int kUninitializedPlayerId = -1;
Expand Down Expand Up @@ -435,7 +439,9 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
_creatingCompleter!.complete(null);
final Completer<void> initializingCompleter = Completer<void>();

void eventListener(VideoEvent event) {
// Set up event listener BEFORE calling prepare() to ensure we don't miss
// the initialized event (two-phase initialization)
_eventListener = (VideoEvent event) {
if (_isDisposed) {
return;
}
Expand Down Expand Up @@ -466,6 +472,8 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
}
_applyLooping();
_applyVolume();
// Note: Native side already handles play/pause restoration in OnRestoreCompleted()
// We only need to apply current state, not trigger play() again
if (VideoEventType.restored == event.eventType &&
_onRestoreDataSource != null) {
play();
Expand Down Expand Up @@ -509,7 +517,7 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
case VideoEventType.unknown:
break;
}
}
};

if (closedCaptionFile != null) {
_closedCaptionFile ??= await closedCaptionFile;
Expand All @@ -529,7 +537,7 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
});
}

void errorListener(Object obj) {
_errorListener = (Object obj) {
final PlatformException e = obj as PlatformException;
value = VideoPlayerValue.erroneous(e.message!);
if (!initializingCompleter.isCompleted) {
Expand All @@ -540,11 +548,18 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
if (!initializingCompleter.isCompleted) {
initializingCompleter.completeError(obj);
}
}
};

_eventSubscription = _videoPlayerPlatform
.videoEventsFor(_playerId)
.listen(eventListener, onError: errorListener);
.listen(_eventListener, onError: _errorListener);

// Two-phase initialization: Call prepare() AFTER setting up event listeners
// This ensures the initialized event is not missed
// For Tizen, prepare() starts player_prepare_async
// For other platforms, prepare() is a no-op (prepare already done in create())
await _videoPlayerPlatform.prepare(_playerId);

return initializingCompleter.future;
}

Expand Down Expand Up @@ -630,7 +645,7 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
return Timer.periodic(const Duration(milliseconds: 500), (
Timer timer,
) async {
if (_isDisposed) {
if (_isDisposed || _isDisposedOrNotInitialized) {
return;
}
final Duration? newPosition = await position;
Expand Down Expand Up @@ -904,6 +919,7 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
}

/// Restores the player state when the application is resumed.
/// Player ID remains unchanged after restore.
Future<void> _restore() async {
if (_isDisposedOrNotInitialized) {
return;
Expand All @@ -913,11 +929,13 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
(_onRestoreDataSource != null) ? _onRestoreDataSource!() : null;
final int resumeTime = (_onRestoreTime != null) ? _onRestoreTime!() : -1;

// P0-3 fix: restore returns void, player ID remains unchanged
await _videoPlayerPlatform.restore(
_playerId,
dataSource: dataSource,
resumeTime: resumeTime,
);
// Player ID remains unchanged, no need to update event subscription
}

/// Set the rotate angle of display
Expand All @@ -928,6 +946,18 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {

return _videoPlayerPlatform.setDisplayRotate(_playerId, rotation);
}

/// Check if the video is a live stream.
/// Returns true if live, false if VOD.
///
/// This method should be called after the video is initialized.
/// The result is cached in native side to avoid repeated API calls.
Future<bool> isLive() async {
if (_isDisposedOrNotInitialized) {
return false;
}
return _videoPlayerPlatform.isLive(_playerId);
}
}

class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,26 @@ abstract class VideoPlayerPlatform extends PlatformInterface {
}

/// Creates an instance of a video player and returns its playerId.
///
/// For two-phase initialization, this method only creates the player
/// without starting playback preparation. Call [prepare()] separately
/// after setting up event listeners.
Future<int?> create(DataSource dataSource) {
throw UnimplementedError('create() has not been implemented.');
}

/// Prepares the player for playback (two-phase initialization).
///
/// This method starts the asynchronous preparation for playback.
/// It must be called after [create()] and after setting up event listeners
/// to ensure the initialized event is not missed.
///
/// For platforms that don't support two-phase initialization,
/// this method is a no-op.
Future<void> prepare(int playerId) {
throw UnimplementedError('prepare() has not been implemented.');
}

/// Returns a Stream of [VideoEventType]s.
Stream<VideoEvent> videoEventsFor(int playerId) {
throw UnimplementedError('videoEventsFor() has not been implemented.');
Expand Down Expand Up @@ -159,6 +175,7 @@ abstract class VideoPlayerPlatform extends PlatformInterface {
}

/// Restores the player state when the application is resumed.
/// Player ID remains unchanged after restore.
Future<void> restore(
int playerId, {
DataSource? dataSource,
Expand All @@ -171,6 +188,12 @@ abstract class VideoPlayerPlatform extends PlatformInterface {
Future<bool> setDisplayRotate(int playerId, DisplayRotation rotation) {
throw UnimplementedError('setDisplayRotate() has not been implemented.');
}

/// Check if the video is a live stream.
/// Returns true if live, false if VOD.
Future<bool> isLive(int playerId) {
throw UnimplementedError('isLive() has not been implemented.');
}
}

/// Description of the data source used to create an instance of
Expand Down
11 changes: 9 additions & 2 deletions packages/video_player_videohole/tizen/src/drm_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ bool DrmManager::SetChallenge(const std::string &media_url,
return DM_ERROR_NONE == SetChallenge(media_url);
}

void DrmManager::ReleaseDrmSession() {
void DrmManager::StopDrmSession() {
if (drm_session_ == nullptr) {
LOG_ERROR("[DrmManager] Already released.");
return;
Expand All @@ -138,8 +138,15 @@ void DrmManager::ReleaseDrmSession() {
LOG_ERROR("[DrmManager] Fail to set finalize to drm session: %s",
get_error_message(ret));
}
}

void DrmManager::ReleaseDrmSession() {
if (drm_session_ == nullptr) {
LOG_ERROR("[DrmManager] Already released.");
return;
}

ret = DMGRReleaseDRMSession(drm_session_);
int ret = DMGRReleaseDRMSession(drm_session_);
if (ret != DM_ERROR_NONE) {
LOG_ERROR("[DrmManager] Fail to release drm session: %s",
get_error_message(ret));
Expand Down
1 change: 1 addition & 0 deletions packages/video_player_videohole/tizen/src/drm_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class DrmManager {
unsigned char *pssh_data, void *user_data);
int UpdatePsshData(const void *data, int length);
void ReleaseDrmSession();
void StopDrmSession();

private:
struct DataForLicenseProcess {
Expand Down
123 changes: 123 additions & 0 deletions packages/video_player_videohole/tizen/src/ffi_messages.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2023 Samsung Electronics Co., Ltd. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// FFI API header for video_player_tizen
// This file contains all FFI function declarations and message types

#ifndef FFI_MESSAGES_H_
#define FFI_MESSAGES_H_

#include <flutter/binary_messenger.h>
#include <flutter/encodable_value.h>

#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include <variant>

#ifdef __cplusplus
extern "C" {
#endif

// ===== FFI function declarations =====

int ffi_initialize();
int64_t ffi_create(const char* create_message_json);
int ffi_dispose(int64_t player_id);
int ffi_play(int64_t player_id);
int ffi_pause(int64_t player_id);
int ffi_seek_to(int64_t player_id, int64_t position_ms);
int64_t ffi_get_position(int64_t player_id);
const char* ffi_get_duration(int64_t player_id);
int ffi_set_volume(int64_t player_id, double volume);
int ffi_set_playback_speed(int64_t player_id, double speed);
int ffi_set_looping(int64_t player_id, bool is_looping);
const char* ffi_get_track_info(int64_t player_id, const char* track_type);
int ffi_set_track_selection(int64_t player_id, int64_t track_id,
const char* track_type);
int ffi_set_display_geometry(int64_t player_id, int32_t x, int32_t y,
int32_t width, int32_t height);
int ffi_set_display_rotate(int64_t player_id, int32_t rotation);
int ffi_suspend(int64_t player_id);
// P0-3 fix: restore returns int (0 on success, -1 on failure)
// Player ID remains unchanged after restore
int ffi_restore(int64_t player_id, const char* create_message_json,
int64_t resume_time);
int ffi_set_activate(int64_t player_id);
int ffi_set_deactivate(int64_t player_id);
int ffi_set_mix_with_others(bool mix_with_others);

// FFI event port functions
int ffi_initialize_api_dl(void* data);
void ffi_register_event_port(int64_t port);
void ffi_unregister_event_port();

// P0-1 fix: FFI string memory management
void ffi_free_string(char* ptr);

// P0-2 fix: Per-player event port registration
void ffi_register_player_event_port(int64_t player_id, int64_t port);
void ffi_unregister_player_event_port(int64_t player_id);

// P1-2 fix: Unregister all player event ports (for hot restart cleanup)
void ffi_unregister_all_player_event_ports();

#ifdef __cplusplus
} // extern "C"

// ===== Message types for C++ usage =====

namespace video_player_videohole_tizen {

// CreateMessage - player creation parameters (used by FFI create/restore)
class CreateMessage {
public:
CreateMessage() = default;

const std::optional<std::string>& asset() const { return asset_; }
void set_asset(const std::string& value) { asset_ = value; }

const std::optional<std::string>& uri() const { return uri_; }
void set_uri(const std::string& value) { uri_ = value; }

const std::optional<std::string>& package_name() const {
return package_name_;
}
void set_package_name(const std::string& value) { package_name_ = value; }

const std::optional<std::string>& format_hint() const { return format_hint_; }
void set_format_hint(const std::string& value) { format_hint_ = value; }

const flutter::EncodableMap& http_headers() const { return http_headers_; }
void set_http_headers(const flutter::EncodableMap& value) {
http_headers_ = value;
}

const flutter::EncodableMap& drm_configs() const { return drm_configs_; }
void set_drm_configs(const flutter::EncodableMap& value) {
drm_configs_ = value;
}

const flutter::EncodableMap& player_options() const {
return player_options_;
}
void set_player_options(const flutter::EncodableMap& value) {
player_options_ = value;
}

private:
std::optional<std::string> asset_;
std::optional<std::string> uri_;
std::optional<std::string> package_name_;
std::optional<std::string> format_hint_;
flutter::EncodableMap http_headers_;
flutter::EncodableMap drm_configs_;
flutter::EncodableMap player_options_;
};

} // namespace video_player_videohole_tizen

#endif // __cplusplus
#endif // FFI_MESSAGES_H_
Loading
Loading