Skip to content

[video_player_android] Avoid sending unset duration on initialization#11709

Open
dao-wkm wants to merge 2 commits into
flutter:mainfrom
dao-wkm:fix-video-player-android-time-unset-duration
Open

[video_player_android] Avoid sending unset duration on initialization#11709
dao-wkm wants to merge 2 commits into
flutter:mainfrom
dao-wkm:fix-video-player-android-time-unset-duration

Conversation

@dao-wkm
Copy link
Copy Markdown

@dao-wkm dao-wkm commented May 14, 2026

Description

Fixes flutter/flutter#176575

Avoid sending an initialized event with an unset duration in video_player_android.

On Android, ExoPlayer can briefly report C.TIME_UNSET for non-live, non-dynamic media when playback first reaches STATE_READY. That value can be converted into an invalid Duration on the Dart side, causing some videos to report a duration such as 0:00:00.001000.

This PR delays sending the initialized event until a valid duration is available for regular media. It also listens for timeline updates and retries initialization when the timeline becomes ready. A short fallback timeout is kept so initialization is not blocked indefinitely if a valid duration is never reported.

Breaking Changes

None - all changes are additive and backward compatible.

Pre-Review Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools.
  • I read the [Tree Hygiene] page, which explains my responsibilities.
  • I read and followed the [relevant style guides] and ran [the auto-formatter].
  • I signed the [CLA].
  • The title of the PR starts with the name of the package surrounded by square brackets, e.g. [shared_preferences]
  • I [linked to at least one issue that this PR fixes] in the description above.
  • I followed [the version and CHANGELOG instructions], using [semantic versioning] and the [repository CHANGELOG style], or I have commented below to indicate which documented exception this PR falls under[^1].
  • I updated/added any relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or I have commented below to indicate which [test exemption] this PR falls under[^1].
  • All existing and new tests are passing.

@flutter-dashboard
Copy link
Copy Markdown

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@google-cla
Copy link
Copy Markdown

google-cla Bot commented May 14, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modifies ExoPlayerEventListener to delay the initialization event until a valid media duration is available, implementing a 500ms fallback mechanism using a Handler. Feedback suggests adding a release method to cancel pending callbacks and prevent potential crashes upon disposal. Additionally, the reviewer recommended ensuring the fallback timer is not repeatedly reset and requested the inclusion of unit tests to verify the new logic and timeout behavior.

@@ -51,6 +65,32 @@ public ExoPlayerEventListener(

protected abstract void sendInitialized();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The use of Handler.postDelayed introduces a risk of crashing if the player is disposed before the timeout expires. If initializationFallback runs after exoPlayer.release(), calling sendInitialized() (which typically accesses the player) will likely result in an IllegalStateException.

Please add a cleanup method to cancel the pending callback, and ensure the VideoPlayer implementation stores the listener and calls this method during disposal.

  protected abstract void sendInitialized();

  /**
   * Cancels any pending initialization callbacks.
   *
   * <p>This should be called when the listener is no longer needed to prevent memory leaks or
   * crashes from delayed execution.
   */
  public void release() {
    mainHandler.removeCallbacks(initializationFallback);
  }

Comment on lines +81 to +86
if (!hasValidDuration() && shouldWaitForValidDuration()) {
isWaitingForValidDuration = true;
mainHandler.removeCallbacks(initializationFallback);
mainHandler.postDelayed(initializationFallback, DURATION_UNSET_INITIALIZATION_TIMEOUT_MS);
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation resets the initialization fallback timer on every call to maybeSendInitialized if the duration is still unset. If there are frequent timeline updates or state changes, this could delay the fallback initialization significantly beyond the intended 500ms.

Consider only posting the delayed runnable if isWaitingForValidDuration is not already true to ensure the fallback fires within the expected timeframe.

Suggested change
if (!hasValidDuration() && shouldWaitForValidDuration()) {
isWaitingForValidDuration = true;
mainHandler.removeCallbacks(initializationFallback);
mainHandler.postDelayed(initializationFallback, DURATION_UNSET_INITIALIZATION_TIMEOUT_MS);
return;
}
if (!hasValidDuration() && shouldWaitForValidDuration()) {
if (!isWaitingForValidDuration) {
isWaitingForValidDuration = true;
mainHandler.postDelayed(initializationFallback, DURATION_UNSET_INITIALIZATION_TIMEOUT_MS);
}
return;
}

Comment on lines +116 to +120
public void onTimelineChanged(@NonNull Timeline timeline, int reason) {
if (isWaitingForValidDuration && exoPlayer.getPlaybackState() == Player.STATE_READY) {
maybeSendInitialized();
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The PR description mentions that new tests were added, but no changes to ExoPlayerEventListenerTest.java are included in this pull request. The new logic for waiting for a valid duration and the fallback mechanism should be thoroughly tested. The existing tests pass by default because the mock ExoPlayer returns a valid duration (0) instead of C.TIME_UNSET.

Please add unit tests that verify:

  • Initialization is delayed when duration is C.TIME_UNSET for non-live media.
  • Initialization occurs immediately when a valid duration is provided.
  • The fallback mechanism triggers after the timeout if the duration remains unset.
  • onTimelineChanged correctly triggers initialization when the duration becomes available.
References
  1. Code should be tested. Changes to plugin packages should have appropriate tests. (link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[video_player] controller.value.duration returns zero (wrong duration) on some videos (test video attached) on Android

1 participant