Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
[video_player] Fix a disposed VideoPlayerController throwing an exc…
Browse files Browse the repository at this point in the history
…eption when being replaced in the `VideoPlayer` (#4344)

* fix: do not removeListener if VideoPlayerController is already disposed

Co-authored-by: David Iglesias Teixeira <[email protected]>
  • Loading branch information
bselwe and ditman authored Sep 15, 2021
1 parent bb3ebb2 commit af758d5
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 1 deletion.
4 changes: 4 additions & 0 deletions packages/video_player/video_player/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.2.2

* Fix a disposed `VideoPlayerController` throwing an exception when being replaced in the `VideoPlayer`.

## 2.2.1

* Specify Java 8 for Android build.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// 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/foundation.dart';
import 'package:flutter/material.dart';
import 'package:integration_test/integration_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:video_player/video_player.dart';

const Duration _playDuration = Duration(seconds: 1);

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets(
'can substitute one controller by another without crashing',
(WidgetTester tester) async {
VideoPlayerController controller = VideoPlayerController.network(
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
);
VideoPlayerController another = VideoPlayerController.network(
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
);
await controller.initialize();
await another.initialize();
await controller.setVolume(0);
await another.setVolume(0);

final Completer<void> started = Completer();
final Completer<void> ended = Completer();
bool startedBuffering = false;
bool endedBuffering = false;

another.addListener(() {
if (another.value.isBuffering && !startedBuffering) {
startedBuffering = true;
started.complete();
}
if (startedBuffering && !another.value.isBuffering && !endedBuffering) {
endedBuffering = true;
ended.complete();
}
});

// Inject a widget with `controller`...
await tester.pumpWidget(renderVideoWidget(controller));
await controller.play();
await tester.pumpAndSettle(_playDuration);
await controller.pause();

// Disposing controller causes the Widget to crash in the next line
// (Issue https://github.com/flutter/flutter/issues/90046)
await controller.dispose();

// Now replace it with `another` controller...
await tester.pumpWidget(renderVideoWidget(another));
await another.play();
await another.seekTo(const Duration(seconds: 5));
await tester.pumpAndSettle(_playDuration);
await another.pause();

// Expect that `another` played.
expect(another.value.position,
(Duration position) => position > const Duration(seconds: 0));

await started;
expect(startedBuffering, true);

await ended;
expect(endedBuffering, true);
},
skip: !(kIsWeb || defaultTargetPlatform == TargetPlatform.android),
);
}

Widget renderVideoWidget(VideoPlayerController controller) {
return Material(
elevation: 0,
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: AspectRatio(
key: Key('same'),
aspectRatio: controller.value.aspectRatio,
child: VideoPlayer(controller),
),
),
),
);
}
9 changes: 9 additions & 0 deletions packages/video_player/video_player/lib/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,15 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
value = value.copyWith(caption: _getCaptionAt(position));
}

@override
void removeListener(VoidCallback listener) {
// Prevent VideoPlayer from causing an exception to be thrown when attempting to
// remove its own listener after the controller has already been disposed.
if (!_isDisposed) {
super.removeListener(listener);
}
}

bool get _isDisposedOrNotInitialized => _isDisposed || !value.isInitialized;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/video_player/video_player/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter
widgets on Android, iOS, and web.
repository: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
version: 2.2.1
version: 2.2.2

environment:
sdk: ">=2.12.0 <3.0.0"
Expand Down

0 comments on commit af758d5

Please sign in to comment.