diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index d01c0ec1d9a..8593f312380 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,5 +1,7 @@ -## NEXT +## 2.10.1 +* Fixes a bug where the `VideoPlayer` widget and `VideoProgressIndicator` widget would stop updating after GlobalKey reparenting. +* Updates the `VideoProgressIndicator` widget to handle zero-duration videos. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 2.10.0 diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 8f8ebf5d8dc..c1a7bbccbd1 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math' as math show max; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -79,7 +80,7 @@ class VideoPlayerValue { /// The total duration of the video. /// - /// The duration is [Duration.zero] if the video hasn't been initialized. + /// The value is only meaningful when [isInitialized] is true. final Duration duration; /// The current playback position. @@ -430,7 +431,7 @@ class VideoPlayerController extends ValueNotifier { _lifeCycleObserver?.initialize(); _creatingCompleter = Completer(); - late DataSource dataSourceDescription; + final DataSource dataSourceDescription; switch (dataSourceType) { case DataSourceType.asset: dataSourceDescription = DataSource( @@ -863,20 +864,15 @@ class VideoPlayer extends StatefulWidget { } class _VideoPlayerState extends State { - _VideoPlayerState() { - _listener = () { - final int newPlayerId = widget.controller.playerId; - if (newPlayerId != _playerId) { - setState(() { - _playerId = newPlayerId; - }); - } - }; - } - - late VoidCallback _listener; - late int _playerId; + void _controllerDidUpdateValue() { + final int newPlayerId = widget.controller.playerId; + if (newPlayerId != _playerId) { + setState(() { + _playerId = newPlayerId; + }); + } + } @override void initState() { @@ -884,21 +880,21 @@ class _VideoPlayerState extends State { _playerId = widget.controller.playerId; // Need to listen for initialization events since the actual widget ID // becomes available after asynchronous initialization finishes. - widget.controller.addListener(_listener); + widget.controller.addListener(_controllerDidUpdateValue); } @override void didUpdateWidget(VideoPlayer oldWidget) { super.didUpdateWidget(oldWidget); - oldWidget.controller.removeListener(_listener); + oldWidget.controller.removeListener(_controllerDidUpdateValue); _playerId = widget.controller.playerId; - widget.controller.addListener(_listener); + widget.controller.addListener(_controllerDidUpdateValue); } @override - void deactivate() { - super.deactivate(); - widget.controller.removeListener(_listener); + void dispose() { + widget.controller.removeListener(_controllerDidUpdateValue); + super.dispose(); } @override @@ -1089,58 +1085,52 @@ class VideoProgressIndicator extends StatefulWidget { } class _VideoProgressIndicatorState extends State { - _VideoProgressIndicatorState() { - listener = () { - if (!mounted) { - return; - } - setState(() {}); - }; - } - - late VoidCallback listener; - VideoPlayerController get controller => widget.controller; VideoProgressColors get colors => widget.colors; + void _didUpdateControllerValue() { + setState(() { + // The build method reads from controller.value. + }); + } + @override void initState() { super.initState(); - controller.addListener(listener); + controller.addListener(_didUpdateControllerValue); } @override - void deactivate() { - controller.removeListener(listener); - super.deactivate(); + void dispose() { + controller.removeListener(_didUpdateControllerValue); + super.dispose(); } @override Widget build(BuildContext context) { - Widget progressIndicator; + final Widget progressIndicator; if (controller.value.isInitialized) { final int duration = controller.value.duration.inMilliseconds; final int position = controller.value.position.inMilliseconds; - int maxBuffering = 0; - for (final DurationRange range in controller.value.buffered) { - final int end = range.end.inMilliseconds; - if (end > maxBuffering) { - maxBuffering = end; - } - } - + final double maxBuffering = + duration == 0.0 + ? 0.0 + : controller.value.buffered + .map((DurationRange range) => range.end.inMilliseconds) + .fold(0, math.max) / + duration; progressIndicator = Stack( fit: StackFit.passthrough, children: [ LinearProgressIndicator( - value: maxBuffering / duration, + value: maxBuffering, valueColor: AlwaysStoppedAnimation(colors.bufferedColor), backgroundColor: colors.backgroundColor, ), LinearProgressIndicator( - value: position / duration, + value: duration == 0.0 ? 0.0 : position / duration, valueColor: AlwaysStoppedAnimation(colors.playedColor), backgroundColor: Colors.transparent, ), diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index c8863f632ff..956bbdd7180 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, macOS and web. repository: https://github.com/flutter/packages/tree/main/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.10.0 +version: 2.10.1 environment: sdk: ^3.7.0 diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart index 8ded8dd56e6..2e5324fa050 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -172,6 +172,87 @@ void main() { ); }); + testWidgets( + 'VideoPlayer still listens for controller changes when reparented', + (WidgetTester tester) async { + final FakeController controller = FakeController(); + addTearDown(controller.dispose); + final GlobalKey videoKey = GlobalKey(); + final Widget videoPlayer = KeyedSubtree( + key: videoKey, + child: VideoPlayer(controller), + ); + + await tester.pumpWidget(videoPlayer); + expect(find.byType(Texture), findsNothing); + + // The VideoPlayer is reparented in the widget tree, before the + // underlying player is initialized. + await tester.pumpWidget(SizedBox(child: videoPlayer)); + controller.playerId = 321; + controller.value = controller.value.copyWith( + duration: const Duration(milliseconds: 100), + isInitialized: true, + ); + + await tester.pump(); + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Texture && widget.textureId == 321, + ), + findsOneWidget, + ); + }, + ); + + testWidgets( + 'VideoProgressIndicator still listens for controller changes after reparenting', + (WidgetTester tester) async { + final FakeController controller = FakeController(); + addTearDown(controller.dispose); + final GlobalKey key = GlobalKey(); + final Widget progressIndicator = VideoProgressIndicator( + key: key, + controller, + allowScrubbing: false, + ); + + controller.value = controller.value.copyWith( + duration: const Duration(milliseconds: 100), + position: const Duration(milliseconds: 50), + isInitialized: true, + ); + await tester.pumpWidget(MaterialApp(home: progressIndicator)); + await tester.pump(); + await tester.pumpWidget( + MaterialApp(home: SizedBox(child: progressIndicator)), + ); + expect((key.currentContext! as Element).dirty, isFalse); + // Verify that changing value dirties the widget tree. + controller.value = controller.value.copyWith( + position: const Duration(milliseconds: 100), + ); + expect((key.currentContext! as Element).dirty, isTrue); + }, + ); + + testWidgets('VideoPlayer does not crash after loading 0-duration videos', ( + WidgetTester tester, + ) async { + final FakeController controller = FakeController(); + addTearDown(controller.dispose); + controller.value = controller.value.copyWith( + duration: Duration.zero, + isInitialized: true, + ); + await tester.pumpWidget( + MaterialApp( + home: VideoProgressIndicator(controller, allowScrubbing: false), + ), + ); + expect(tester.takeException(), isNull); + }); + testWidgets('non-zero rotationCorrection value is used', ( WidgetTester tester, ) async { diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index 52d6dca4366..43cb8b7fd29 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.8.14 + +* Fixes an issue in the example app that some widgets stop updating after GlobalKey reparenting. +* Updates the `VideoProgressIndicator` widget in the example app to handle zero-duration videos. + ## 2.8.13 * Bumps com.android.tools.build:gradle to 8.12.1. diff --git a/packages/video_player/video_player_android/example/lib/mini_controller.dart b/packages/video_player/video_player_android/example/lib/mini_controller.dart index ec52735d09f..019d4633442 100644 --- a/packages/video_player/video_player_android/example/lib/mini_controller.dart +++ b/packages/video_player/video_player_android/example/lib/mini_controller.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math' as math show max; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -433,9 +434,9 @@ class _VideoPlayerState extends State { } @override - void deactivate() { - super.deactivate(); + void dispose() { widget.controller.removeListener(_listener); + super.dispose(); } @override @@ -535,9 +536,9 @@ class _VideoProgressIndicatorState extends State { } @override - void deactivate() { + void dispose() { controller.removeListener(listener); - super.deactivate(); + super.dispose(); } @override @@ -546,29 +547,29 @@ class _VideoProgressIndicatorState extends State { const Color bufferedColor = Color.fromRGBO(50, 50, 200, 0.2); const Color backgroundColor = Color.fromRGBO(200, 200, 200, 0.5); - Widget progressIndicator; + final Widget progressIndicator; if (controller.value.isInitialized) { final int duration = controller.value.duration.inMilliseconds; final int position = controller.value.position.inMilliseconds; - int maxBuffering = 0; - for (final DurationRange range in controller.value.buffered) { - final int end = range.end.inMilliseconds; - if (end > maxBuffering) { - maxBuffering = end; - } - } + final double maxBuffering = + duration == 0.0 + ? 0.0 + : controller.value.buffered + .map((DurationRange range) => range.end.inMilliseconds) + .fold(0, math.max) / + duration; progressIndicator = Stack( fit: StackFit.passthrough, children: [ LinearProgressIndicator( - value: maxBuffering / duration, + value: maxBuffering, valueColor: const AlwaysStoppedAnimation(bufferedColor), backgroundColor: backgroundColor, ), LinearProgressIndicator( - value: position / duration, + value: duration == 0.0 ? 0.0 : position / duration, valueColor: const AlwaysStoppedAnimation(playedColor), backgroundColor: Colors.transparent, ), diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 00129de08bb..ac12f3df83b 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_android description: Android implementation of the video_player plugin. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.8.13 +version: 2.8.14 environment: sdk: ^3.7.0 diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index d358aae1f1c..551534ab776 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,5 +1,8 @@ -## NEXT +## 2.8.5 +* Fixes a bug where the video player fails to initialize when `AVFoundation` reports a duration of zero. +* Fixes a bug in the example app that some widgets stop updating after GlobalKey reparenting. +* Updates the `VideoProgressIndicator` widget in the example app to handle zero-duration videos. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 2.8.4 diff --git a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m index fc3716e427e..b095dbf33ae 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m @@ -936,6 +936,33 @@ - (void)testPlayerShouldNotDropEverySecondFrame { OCMVerifyAllWithDelay(mockTextureRegistry, 10); } +- (void)testVideoOutputIsAddedWhenAVPlayerItemBecomesReady { + NSObject *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); + FVPVideoPlayerPlugin *videoPlayerPlugin = + [[FVPVideoPlayerPlugin alloc] initWithRegistrar:registrar]; + FlutterError *error; + [videoPlayerPlugin initialize:&error]; + XCTAssertNil(error); + FVPCreationOptions *create = [FVPCreationOptions + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" + httpHeaders:@{}]; + + FVPTexturePlayerIds *identifiers = [videoPlayerPlugin createTexturePlayerWithOptions:create + error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(identifiers); + FVPVideoPlayer *player = videoPlayerPlugin.playersByIdentifier[@(identifiers.playerId)]; + XCTAssertNotNil(player); + + AVPlayerItem *item = player.player.currentItem; + [self keyValueObservingExpectationForObject:(id)item + keyPath:@"status" + expectedValue:@(AVPlayerItemStatusReadyToPlay)]; + [self waitForExpectationsWithTimeout:10.0 handler:nil]; + // Video output is added as soon as the status becomes ready to play. + XCTAssertEqual(item.outputs.count, 1); +} + #if TARGET_OS_IOS - (void)testVideoPlayerShouldNotOverwritePlayAndRecordNorDefaultToSpeaker { NSObject *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m index ea1084b9dd8..bed65782c5d 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m @@ -11,8 +11,6 @@ static void *timeRangeContext = &timeRangeContext; static void *statusContext = &statusContext; -static void *presentationSizeContext = &presentationSizeContext; -static void *durationContext = &durationContext; static void *playbackLikelyToKeepUpContext = &playbackLikelyToKeepUpContext; static void *rateContext = &rateContext; @@ -64,8 +62,6 @@ static void FVPRemoveKeyValueObservers(NSObject *observer, return @{ @"loadedTimeRanges" : [NSValue valueWithPointer:timeRangeContext], @"status" : [NSValue valueWithPointer:statusContext], - @"presentationSize" : [NSValue valueWithPointer:presentationSizeContext], - @"duration" : [NSValue valueWithPointer:durationContext], @"playbackLikelyToKeepUp" : [NSValue valueWithPointer:playbackLikelyToKeepUpContext], }; } @@ -264,14 +260,6 @@ - (void)observeValueForKeyPath:(NSString *)path } else if (context == statusContext) { AVPlayerItem *item = (AVPlayerItem *)object; [self reportStatusForPlayerItem:item]; - } else if (context == presentationSizeContext || context == durationContext) { - AVPlayerItem *item = (AVPlayerItem *)object; - if (item.status == AVPlayerItemStatusReadyToPlay) { - // Due to an apparent bug, when the player item is ready, it still may not have determined - // its presentation size or duration. When these properties are finally set, re-check if - // all required properties and instantiate the event sink if it is not already set up. - [self reportInitializedIfReadyToPlay]; - } } else if (context == playbackLikelyToKeepUpContext) { [self updatePlayingState]; if ([[_player currentItem] isPlaybackLikelyToKeepUp]) { @@ -288,6 +276,8 @@ - (void)observeValueForKeyPath:(NSString *)path } - (void)reportStatusForPlayerItem:(AVPlayerItem *)item { + NSAssert(self.eventListener, + @"reportStatusForPlayerItem was called when the event listener was not set."); switch (item.status) { case AVPlayerItemStatusFailed: [self sendFailedToLoadVideoEvent]; @@ -295,8 +285,10 @@ - (void)reportStatusForPlayerItem:(AVPlayerItem *)item { case AVPlayerItemStatusUnknown: break; case AVPlayerItemStatusReadyToPlay: - [item addOutput:_videoOutput]; - [self reportInitializedIfReadyToPlay]; + if (!_isInitialized) { + [item addOutput:_videoOutput]; + [self reportInitializedIfReadyToPlay]; + } break; } } @@ -370,52 +362,15 @@ - (void)sendFailedToLoadVideoEvent { } - (void)reportInitializedIfReadyToPlay { - if (!_isInitialized) { - AVPlayerItem *currentItem = self.player.currentItem; - CGSize size = currentItem.presentationSize; - CGFloat width = size.width; - CGFloat height = size.height; - - // Wait until tracks are loaded to check duration or if there are any videos. - AVAsset *asset = currentItem.asset; - if ([asset statusOfValueForKey:@"tracks" error:nil] != AVKeyValueStatusLoaded) { - void (^trackCompletionHandler)(void) = ^{ - if ([asset statusOfValueForKey:@"tracks" error:nil] != AVKeyValueStatusLoaded) { - // Cancelled, or something failed. - return; - } - // This completion block will run on an AVFoundation background queue. - // Hop back to the main thread to set up event sink. - [self performSelector:_cmd onThread:NSThread.mainThread withObject:self waitUntilDone:NO]; - }; - [asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] - completionHandler:trackCompletionHandler]; - return; - } + AVPlayerItem *currentItem = self.player.currentItem; + NSAssert(currentItem.status == AVPlayerItemStatusReadyToPlay, + @"reportInitializedIfReadyToPlay was called when the item wasn't ready to play."); + NSAssert(!_isInitialized, @"reportInitializedIfReadyToPlay should only be called once."); - BOOL hasVideoTracks = [asset tracksWithMediaType:AVMediaTypeVideo].count != 0; - // Audio-only HLS files have no size, so `currentItem.tracks.count` must be used to check for - // track presence, as AVAsset does not always provide track information in HLS streams. - BOOL hasNoTracks = currentItem.tracks.count == 0 && asset.tracks.count == 0; - - // The player has not yet initialized when it has no size, unless it is an audio-only track. - // HLS m3u8 video files never load any tracks, and are also not yet initialized until they have - // a size. - if ((hasVideoTracks || hasNoTracks) && height == CGSizeZero.height && - width == CGSizeZero.width) { - return; - } - // The player may be initialized but still needs to determine the duration. - int64_t duration = [self duration]; - if (duration == 0) { - return; - } - - _isInitialized = YES; - [self updatePlayingState]; - - [self.eventListener videoPlayerDidInitializeWithDuration:duration size:size]; - } + _isInitialized = YES; + [self updatePlayingState]; + [self.eventListener videoPlayerDidInitializeWithDuration:self.duration + size:currentItem.presentationSize]; } #pragma mark - FVPVideoPlayerInstanceApi diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h index 27b1bdf9389..d249391f73a 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h @@ -22,7 +22,14 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) NSNumber *targetPlaybackSpeed; /// Indicates whether the video player is currently playing. @property(nonatomic, readonly) BOOL isPlaying; -/// Indicates whether the video player has been initialized. +/// Indicates whether an "initialized" message has been sent to the current Flutter event listener. +/// +/// The video player sends an "initialized" message to the event listener when its underlying +/// AVPlayerItem is ready to play and the event listener is set to a non-nil value, whichever occurs +/// last. +/// +/// This flag is set to YES when the "initialized" message is first sent, and is never set to NO +/// again. @property(nonatomic, readonly) BOOL isInitialized; /// Updates the playing state of the video player. diff --git a/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart b/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart index 235b09bea8a..27a5297f697 100644 --- a/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart +++ b/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math' as math show max; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -16,11 +17,7 @@ import 'package:video_player_platform_interface/video_player_platform_interface. VideoPlayerPlatform? _cachedPlatform; VideoPlayerPlatform get _platform { - if (_cachedPlatform == null) { - _cachedPlatform = VideoPlayerPlatform.instance; - _cachedPlatform!.init(); - } - return _cachedPlatform!; + return _cachedPlatform ??= VideoPlayerPlatform.instance..init(); } /// The duration, current position, buffering state, error state and settings @@ -424,8 +421,8 @@ class _VideoPlayerState extends State { } @override - void deactivate() { - super.deactivate(); + void dispose() { + super.dispose(); widget.controller.removeListener(_listener); } @@ -517,29 +514,28 @@ class _VideoProgressIndicatorState extends State { const Color bufferedColor = Color.fromRGBO(50, 50, 200, 0.2); const Color backgroundColor = Color.fromRGBO(200, 200, 200, 0.5); - Widget progressIndicator; + final Widget progressIndicator; if (controller.value.isInitialized) { final int duration = controller.value.duration.inMilliseconds; final int position = controller.value.position.inMilliseconds; - int maxBuffering = 0; - for (final DurationRange range in controller.value.buffered) { - final int end = range.end.inMilliseconds; - if (end > maxBuffering) { - maxBuffering = end; - } - } - + final double maxBuffering = + duration == 0.0 + ? 0.0 + : controller.value.buffered + .map((DurationRange range) => range.end.inMilliseconds) + .fold(0, math.max) / + duration; progressIndicator = Stack( fit: StackFit.passthrough, children: [ LinearProgressIndicator( - value: maxBuffering / duration, + value: maxBuffering, valueColor: const AlwaysStoppedAnimation(bufferedColor), backgroundColor: backgroundColor, ), LinearProgressIndicator( - value: position / duration, + value: duration == 0.0 ? 0.0 : position / duration, valueColor: const AlwaysStoppedAnimation(playedColor), backgroundColor: Colors.transparent, ), diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index 8675038ba86..207c7ea58d5 100644 --- a/packages/video_player/video_player_avfoundation/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_avfoundation description: iOS and macOS implementation of the video_player plugin. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.8.4 +version: 2.8.5 environment: sdk: ^3.7.0