From 713c7320e6d3038c65c66da65a1a1f6ee83bf1a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:29:21 +0000 Subject: [PATCH 1/3] Initial plan From e3714dfab226b5dd556244f084424198ea7d7836 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:33:48 +0000 Subject: [PATCH 2/3] feat: add appendBuffer() support to FVPControllerExtensions Agent-Logs-Url: https://github.com/wang-bin/fvp/sessions/e88de061-ce8a-4b1a-b737-79d4db5109b8 Co-authored-by: wang-bin <785206+wang-bin@users.noreply.github.com> --- lib/src/controller.dart | 10 ++++++++++ lib/src/player.dart | 20 ++++++++++++++++++++ lib/src/video_player_dummy.dart | 4 ++++ lib/src/video_player_mdk.dart | 4 ++++ 4 files changed, 38 insertions(+) diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 0da6b35..45d0646 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -211,4 +211,14 @@ extension FVPControllerExtensions on VideoPlayerController { void setExternalSubtitle(String uri) { _platform.setExternalSubtitle(_getId(this), uri); } + + /// Append media data to the player. Used together with a `mdk:` media source URL + /// to feed data incrementally. The [VideoPlayerController] will not finish + /// initializing until enough data has been appended via this method. + /// [flags] can be 0 for normal data, or -1 to signal end-of-stream. + /// Returns true on success. + /// https://github.com/wang-bin/mdk-sdk/wiki/Player-APIs#bool-appendbufferconst-uint8_t-data-size_t-size-int-flags + bool appendBuffer(Uint8List data, {int flags = 0}) { + return _platform.appendBuffer(_getId(this), data, flags: flags); + } } diff --git a/lib/src/player.dart b/lib/src/player.dart index 3b89ccb..ce13f8b 100644 --- a/lib/src/player.dart +++ b/lib/src/player.dart @@ -496,6 +496,26 @@ class Player { return _seeked!.future; } + /// Append media data to the player. Used with a `mdk:` media source URL to feed data + /// incrementally (similar to MSE). The player will not finish [prepare] until + /// enough data has been appended. + /// [flags] can be 0 for normal data, or -1 to signal end-of-stream. + /// Returns true on success. + /// https://github.com/wang-bin/mdk-sdk/wiki/Player-APIs#bool-appendbufferconst-uint8_t-data-size_t-size-int-flags + bool appendBuffer(Uint8List data, {int flags = 0}) { + final fn = _player.ref.appendBuffer.asFunction< + bool Function(Pointer, Pointer, int, int)>(); + if (data.isEmpty) { + return fn(_player.ref.object, nullptr, 0, flags); + } + // The native appendBuffer copies the data synchronously before returning. + final pointer = malloc(data.length); + pointer.asTypedList(data.length).setAll(0, data); + final result = fn(_player.ref.object, pointer, data.length, flags); + malloc.free(pointer); + return result; + } + List bufferedTimeRanges() { const int n = 16; final cbytes = calloc(2 * n); diff --git a/lib/src/video_player_dummy.dart b/lib/src/video_player_dummy.dart index 68dad0d..0b23655 100644 --- a/lib/src/video_player_dummy.dart +++ b/lib/src/video_player_dummy.dart @@ -69,4 +69,8 @@ class MdkVideoPlayerPlatform { void setExternalVideo(int playerId, String uri) {} void setExternalSubtitle(int playerId, String uri) {} + + bool appendBuffer(int playerId, Uint8List data, {int flags = 0}) { + return false; + } } diff --git a/lib/src/video_player_mdk.dart b/lib/src/video_player_mdk.dart index 8e97a77..77d73f2 100644 --- a/lib/src/video_player_mdk.dart +++ b/lib/src/video_player_mdk.dart @@ -506,6 +506,10 @@ class MdkVideoPlayerPlatform extends VideoPlayerPlatform { _players[playerId]?.setMedia(uri, mdk.MediaType.subtitle); } + bool appendBuffer(int playerId, Uint8List data, {int flags = 0}) { + return _players[playerId]?.appendBuffer(data, flags: flags) ?? false; + } + Future _seekToWithFlags( int playerId, Duration position, mdk.SeekFlag flags) async { final player = _players[playerId]; From d735af8c7ff1a658fc0289d9c3aa31ec00c5e0c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:04:53 +0000 Subject: [PATCH 3/3] fix: replace broken appendBuffer extension with new FVPController class Agent-Logs-Url: https://github.com/wang-bin/fvp/sessions/6d96f646-9eb0-4e80-a86c-9483aa07c1ed Co-authored-by: wang-bin <785206+wang-bin@users.noreply.github.com> --- lib/src/controller.dart | 80 +++++++++++++++++++++++++++++++-- lib/src/player.dart | 3 +- lib/src/video_player_dummy.dart | 10 ++++- lib/src/video_player_mdk.dart | 58 ++++++++++++++++++++++-- 4 files changed, 141 insertions(+), 10 deletions(-) diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 45d0646..48fc9ed 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -211,14 +211,86 @@ extension FVPControllerExtensions on VideoPlayerController { void setExternalSubtitle(String uri) { _platform.setExternalSubtitle(_getId(this), uri); } +} + +/// A [VideoPlayerController] subclass that pre-creates the underlying player so that +/// [appendBuffer] can be called before or during [initialize]. +/// +/// Use this class instead of [VideoPlayerController] when you need to feed media data +/// incrementally (e.g. with a `mdk:` source URL) using [appendBuffer]. +/// +/// Typical usage: +/// ```dart +/// final ctrl = FVPController.network('mdk:'); +/// final initFuture = ctrl.initialize(); // starts but does not await yet +/// ctrl.appendBuffer(chunk1); +/// ctrl.appendBuffer(chunk2, flags: -1); // flags: -1 signals end-of-stream +/// await initFuture; +/// ``` +class FVPController extends VideoPlayerController { + final int _nativeHandle; + + FVPController.network( + String dataSource, { + VideoFormat? formatHint, + Map httpHeaders = const {}, + VideoPlayerOptions? videoPlayerOptions, + Future? closedCaptionFile, + }) : _nativeHandle = _platform.createPendingPlayer(), + super.network(dataSource, + formatHint: formatHint, + httpHeaders: httpHeaders, + videoPlayerOptions: videoPlayerOptions, + closedCaptionFile: closedCaptionFile); + + FVPController.asset( + String dataSource, { + String? package, + Future? closedCaptionFile, + VideoPlayerOptions? videoPlayerOptions, + }) : _nativeHandle = _platform.createPendingPlayer(), + super.asset(dataSource, + package: package, + closedCaptionFile: closedCaptionFile, + videoPlayerOptions: videoPlayerOptions); + + FVPController.file( + File file, { + Future? closedCaptionFile, + VideoPlayerOptions? videoPlayerOptions, + Map httpHeaders = const {}, + }) : _nativeHandle = _platform.createPendingPlayer(), + super.file(file, + closedCaptionFile: closedCaptionFile, + videoPlayerOptions: videoPlayerOptions, + httpHeaders: httpHeaders); - /// Append media data to the player. Used together with a `mdk:` media source URL - /// to feed data incrementally. The [VideoPlayerController] will not finish - /// initializing until enough data has been appended via this method. + @override + Future initialize() async { + _platform.setNextPlayerHandle(_nativeHandle); + try { + return await super.initialize(); + } finally { + // Safety: clear the hint if create() was never reached (e.g. on error). + _platform.clearNextPlayerHandle(); + } + } + + @override + Future dispose() async { + // Disposes the pre-created player if initialize() was never called. + _platform.discardPendingPlayer(_nativeHandle); + return super.dispose(); + } + + /// Append media data to the player. Works before, during, and after [initialize]. + /// + /// Used together with a `mdk:` source URL to feed data incrementally. [initialize] + /// will not complete until enough data has been appended via this method. /// [flags] can be 0 for normal data, or -1 to signal end-of-stream. /// Returns true on success. /// https://github.com/wang-bin/mdk-sdk/wiki/Player-APIs#bool-appendbufferconst-uint8_t-data-size_t-size-int-flags bool appendBuffer(Uint8List data, {int flags = 0}) { - return _platform.appendBuffer(_getId(this), data, flags: flags); + return _platform.appendBufferByHandle(_nativeHandle, data, flags: flags); } } diff --git a/lib/src/player.dart b/lib/src/player.dart index ce13f8b..eb812ca 100644 --- a/lib/src/player.dart +++ b/lib/src/player.dart @@ -508,7 +508,8 @@ class Player { if (data.isEmpty) { return fn(_player.ref.object, nullptr, 0, flags); } - // The native appendBuffer copies the data synchronously before returning. + // The native appendBuffer copies the data synchronously before returning, + // so the allocated memory can be freed immediately after the call. final pointer = malloc(data.length); pointer.asTypedList(data.length).setAll(0, data); final result = fn(_player.ref.object, pointer, data.length, flags); diff --git a/lib/src/video_player_dummy.dart b/lib/src/video_player_dummy.dart index 0b23655..209c7a0 100644 --- a/lib/src/video_player_dummy.dart +++ b/lib/src/video_player_dummy.dart @@ -70,7 +70,15 @@ class MdkVideoPlayerPlatform { void setExternalSubtitle(int playerId, String uri) {} - bool appendBuffer(int playerId, Uint8List data, {int flags = 0}) { + int createPendingPlayer() => 0; + + void setNextPlayerHandle(int handle) {} + + void clearNextPlayerHandle() {} + + void discardPendingPlayer(int handle) {} + + bool appendBufferByHandle(int handle, Uint8List data, {int flags = 0}) { return false; } } diff --git a/lib/src/video_player_mdk.dart b/lib/src/video_player_mdk.dart index 77d73f2..5a7ec3a 100644 --- a/lib/src/video_player_mdk.dart +++ b/lib/src/video_player_mdk.dart @@ -99,6 +99,12 @@ class MdkVideoPlayer extends mdk.Player { class MdkVideoPlayerPlatform extends VideoPlayerPlatform { static final _players = {}; + // Players indexed by nativeHandle for FVPController to access before textureId is known. + static final _playersByHandle = {}; + // Handles of players that have been promoted into _players (i.e. create() succeeded). + static final _promotedHandles = {}; + // nativeHandle hint set by FVPController.initialize() so create() can reuse the pre-created player. + static int? _nextPlayerHandle; static Map? _globalOpts; static Map? _playerOpts; static int? _maxWidth; @@ -245,13 +251,24 @@ class MdkVideoPlayerPlatform extends VideoPlayerPlatform { @override Future dispose(int playerId) async { - _players.remove(playerId)?.dispose(); + final player = _players.remove(playerId); + if (player != null) { + _promotedHandles.remove(player.nativeHandle); + _playersByHandle.remove(player.nativeHandle); + player.dispose(); + } } @override Future create(DataSource dataSource) async { final uri = _toUri(dataSource); - final player = MdkVideoPlayer(); + // Use a pre-created player if FVPController.initialize() set one up. + final nextHandle = _nextPlayerHandle; + _nextPlayerHandle = null; + final preCreated = nextHandle != null ? _playersByHandle[nextHandle] : null; + final player = preCreated ?? MdkVideoPlayer(); + // Register by nativeHandle so appendBufferByHandle() can find it during prepare(). + _playersByHandle[player.nativeHandle] = player; _log.fine('$hashCode player${player.nativeHandle} create($uri)'); //player.setProperty("keep_open", "1"); @@ -299,6 +316,7 @@ class MdkVideoPlayerPlatform extends VideoPlayerPlatform { if (ret < 0) { // no throw, handle error in controller.addListener _players[-hashCode] = player; + _promotedHandles.add(player.nativeHandle); player.streamCtl.addError(PlatformException( code: 'media open error', message: 'invalid or unsupported media', @@ -315,6 +333,7 @@ class MdkVideoPlayerPlatform extends VideoPlayerPlatform { fit: _fitMaxSize); if (tex < 0) { _players[-hashCode] = player; + _promotedHandles.add(player.nativeHandle); player.streamCtl.addError(PlatformException( code: 'video size error', message: 'invalid or unsupported media with invalid video size', @@ -324,6 +343,7 @@ class MdkVideoPlayerPlatform extends VideoPlayerPlatform { } _log.fine('$hashCode player${player.nativeHandle} textureId/playerId=$tex'); _players[tex] = player; + _promotedHandles.add(player.nativeHandle); return tex; } @@ -506,8 +526,38 @@ class MdkVideoPlayerPlatform extends VideoPlayerPlatform { _players[playerId]?.setMedia(uri, mdk.MediaType.subtitle); } - bool appendBuffer(int playerId, Uint8List data, {int flags = 0}) { - return _players[playerId]?.appendBuffer(data, flags: flags) ?? false; + // FVPController support: create a player before initialize() so appendBuffer() can be called early. + + /// Creates a player and registers it by nativeHandle for [FVPController]. + /// Returns the nativeHandle to use with [setNextPlayerHandle] and [appendBufferByHandle]. + int createPendingPlayer() { + final player = MdkVideoPlayer(); + _playersByHandle[player.nativeHandle] = player; + return player.nativeHandle; + } + + /// Tells [create] to use the pre-created player with [handle] instead of making a new one. + void setNextPlayerHandle(int handle) { + _nextPlayerHandle = handle; + } + + /// Safety: clears the next-player hint if [create] was never called. + void clearNextPlayerHandle() { + _nextPlayerHandle = null; + } + + /// Disposes the pre-created player if [initialize] was never called and it was never promoted. + /// Calling this on an already-promoted or non-existent handle is a safe no-op. + void discardPendingPlayer(int handle) { + if (!_promotedHandles.contains(handle)) { + _playersByHandle.remove(handle)?.dispose(); + } + } + + /// Calls [appendBuffer] on the player identified by [nativeHandle]. + /// Works at any point in the lifecycle: before, during, or after [initialize]. + bool appendBufferByHandle(int handle, Uint8List data, {int flags = 0}) { + return _playersByHandle[handle]?.appendBuffer(data, flags: flags) ?? false; } Future _seekToWithFlags(