Repository navigation
Conversation
1c51345 to
7b685d6
Compare
|
@cursor review |
Bugbot couldn't run — GitHub account mismatchThe GitHub account linked to your Cursor account does not match the PR author. Please ensure you're using the correct GitHub account, or run Bugbot from a team that covers this repository. |
wang-bin
left a comment
There was a problem hiding this comment.
Thanks for the clear write-up and the Windows repro — the diagnosis matches the video_player create/dispose coupling (dispose waits on _creatingCompleter, which only completes after createWithOptions returns).
The overall approach is good: Zone-scoped opt-in cancellation, awaitable disposeAsync(), skipping _videoSize on texture release, and a failed player id so initialize can finish. CI looks green.
Please address the inline notes before merge. Biggest ones: align MdkVideoPlayer with disposeAsync, and clarify/simplify the Future.any + StateError vs platform error path. Also worth documenting that this remains opt-in (default initialize/dispose still hangs on a stalled prepare()).
| // Resolve video_player's createWithOptions Future only after MDK has been | ||
| // stopped and deleted. Its controller can then safely finish dispose(). | ||
| await player.disposeAsync(); | ||
| player.streamCtl.close(); |
There was a problem hiding this comment.
disposeAsync() bypasses MdkVideoPlayer.dispose(), so streamCtl has to be closed here by hand.
Prefer closing the stream controller inside an overridden disposeAsync() on MdkVideoPlayer, and keep dispose() => unawaited(disposeAsync()) so both paths tear down the same way:
@override
Future<void> disposeAsync() async {
if (!streamCtl.isClosed) streamCtl.close();
_initialized = false;
await super.disposeAsync();
}
@override
void dispose() => unawaited(disposeAsync());Then this streamCtl.close() can go away.
There was a problem hiding this comment.
Done, I moved the stream cleanup into MdkVideoPlayer.disposeAsync() and kept dispose() => unawaited(disposeAsync()). I also guard late callbacks, including the textureSize continuation.
| (_) => throw StateError('FVP player creation cancelled')), | ||
| ]), | ||
| zoneValues: {_createCancellationZoneKey: this}, | ||
| ); |
There was a problem hiding this comment.
run() completes with StateError as soon as cancel() fires, while initialize() may still be running and later surfaces a PlatformException via the cancelled event stream.
Callers awaiting run() therefore see a different error than controller.value.
Consider either:
- Zone-wrap only (no
Future.anythrow) and let initialize fail through the normal platform error path, or - Keep the early completion but use one dedicated exception type and document that
initialize's later error is expected.
Also a short usage example in fvp.dart dartdoc would help, since this is opt-in.
There was a problem hiding this comment.
Thanks, I went with option 2. run() and the cancellation event now use FvpCreateCancelledException.
I kept the early completion because video_player can cancel its event subscription during dispose(), leaving the original initialize() future pending. The dartdoc explains the possible later error and that callers should await dispose() for native cleanup.
| player.streamCtl.close(); | ||
| final id = _nextFailureId--; | ||
| _cancelledEvents[id] = Stream<VideoEvent>.error(PlatformException( | ||
| code: 'creation cancelled', message: 'Playback changed')); |
There was a problem hiding this comment.
message: 'Playback changed' is a bit opaque (not a video_player constant I could find). Prefer something explicit like 'FVP player creation cancelled' so apps that surface errorDescription can tell cancel from a real media failure.
There was a problem hiding this comment.
I changed it to FVP player creation cancelled, using the same FvpCreateCancelledException.
| rethrow; | ||
| } | ||
| if (cancellation?.isCancelled ?? false) { | ||
| return _finishCancelledCreate(player); |
There was a problem hiding this comment.
Minor: prepare / textureSize are raced with _untilCancelled, but updateTexture / createTexture only check isCancelled after the await (and in catch). Cancel during texture creation is unlikely to hang like network prepare, but the handling is inconsistent — wrapping with _untilCancelled would match the earlier waits.
There was a problem hiding this comment.
I kept this await because createTexture() is still using the native player handle. Racing it with cancellation would let _finishCancelledCreate() delete the player before texture creation returns.
Once allocation completes, I release the texture before deleting the player. I tested cancellation with allocation delayed by 0, 20, 200 and 1000 ms on Windows and macOS; all textures were released.
| if (dart.library.html) 'src/video_player_dummy.dart'; | ||
|
|
||
| export 'src/controller.dart'; | ||
| export 'src/create_cancellation.dart' show FvpCreateCancellation; |
There was a problem hiding this comment.
Please add a short usage example here (or on FvpCreateCancellation) so callers know this is opt-in and that cancel() must be called before dispose() when initialize may still be pending:
final cancel = FvpCreateCancellation();
try {
await cancel.run(() => controller.initialize());
} finally {
cancel.cancel();
await controller.dispose();
}There was a problem hiding this comment.
Added to the FvpCreateCancellation dartdoc, with cancel() before dispose() and a note that this is opt-in.
7b685d6 to
f462191
Compare
f462191 to
086395c
Compare
|
Thanks for the review! I've updated the patch in 086395c and replied inline. I also updated the test results in the PR description. |
Summary
If a source stalls in MDK
prepare(),createWithOptions()remains pending andVideoPlayerController.dispose()cannot release the player. I reproduced this with a localhost response delayed by 20 seconds on Windows and macOS Release.This adds an opt-in
FvpCreateCancellationscope forinitialize(). Callingcancel()beforedispose()lets FVP stop and delete the pending player, then return a failed ID sovideo_playercan finish disposal. Cancellation usesFvpCreateCancelledExceptionin bothrun()and the event stream. The dartdoc includes a usage example. Without the scope, stalled preparation still blocks disposal.Player.disposeAsync()makes teardown awaitable while keepingvoid dispose().MdkVideoPlayer.disposeAsync()closes the event stream and ignores late callbacks. Texture release skips the video-size wait. I kept texture allocation awaited: deleting the player whilecreateTexture()still uses its native handle would be unsafe.Texture creation and cleanup errors also return a failed ID, avoiding another stuck controller. Platform disposal reports release errors and retains the player for retry while its texture remains registered.
Testing
example/PENDING_CREATE_REPRO.md. It uses a delayed localhost response and writes MDK logs atLevel.ALL. Initial Windows logs at 7b685d6: without cancellation,dispose()timed out after 8 seconds; with cancellation, it completed in 38 ms.a233309.VideoPlayerPlatform.dispose(id)completes cleanup. Cancellation and normal playback are covered. Holding native texture release confirms that controller disposal waits for it.a233309with the same MDK 0.39.0 and FFmpeg libraries.callbacks.cpploader shim; Release tests use the real plugin. Windows and macOS CI builds pass on086395c.The macOS retest with MDK 0.39.0 is still unconfirmed because I lost access to the test machine. I also reproduced an intermittent parallel-player teardown hang on unpatched
fc97343with the same MDK and native stack offsets. It did not occur in the completed Mac run; I haven't identified its trigger.