diff --git a/packages/video_player/video_player/example/lib/audio_tracks_demo.dart b/packages/video_player/video_player/example/lib/audio_tracks_demo.dart index 152082e31c45..40783dbc1551 100644 --- a/packages/video_player/video_player/example/lib/audio_tracks_demo.dart +++ b/packages/video_player/video_player/example/lib/audio_tracks_demo.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:collection'; + import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; @@ -20,8 +22,12 @@ class _AudioTracksDemoState extends State { bool _isLoading = false; String? _error; + // Track previous state to detect relevant changes + bool _wasPlaying = false; + bool _wasInitialized = false; + // Sample video URLs with multiple audio tracks - final List _sampleVideos = [ + static const List _sampleVideos = [ 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8', // Add HLS stream with multiple audio tracks if available @@ -51,13 +57,25 @@ class _AudioTracksDemoState extends State { await _controller!.initialize(); + // Add listener for video player state changes + _controller!.addListener(_onVideoPlayerValueChanged); + + // Initialize tracking variables + _wasPlaying = _controller!.value.isPlaying; + _wasInitialized = _controller!.value.isInitialized; + // Get audio tracks after initialization await _loadAudioTracks(); - + if (!mounted) { + return; + } setState(() { _isLoading = false; }); } catch (e) { + if (!mounted) { + return; + } setState(() { _error = 'Failed to initialize video: $e'; _isLoading = false; @@ -72,10 +90,16 @@ class _AudioTracksDemoState extends State { try { final List tracks = await _controller!.getAudioTracks(); + if (!mounted) { + return; + } setState(() { _audioTracks = tracks; }); } catch (e) { + if (!mounted) { + return; + } setState(() { _error = 'Failed to load audio tracks: $e'; }); @@ -90,10 +114,6 @@ class _AudioTracksDemoState extends State { try { await _controller!.selectAudioTrack(trackId); - // Add a small delay to allow ExoPlayer to process the track selection change - // This is needed because ExoPlayer's track selection update is asynchronous - await Future.delayed(const Duration(milliseconds: 100)); - // Reload tracks to update selection status await _loadAudioTracks(); @@ -113,8 +133,34 @@ class _AudioTracksDemoState extends State { } } + void _onVideoPlayerValueChanged() { + if (!mounted || _controller == null) { + return; + } + + final VideoPlayerValue currentValue = _controller!.value; + bool shouldUpdate = false; + + // Check for relevant state changes that affect UI + if (currentValue.isPlaying != _wasPlaying) { + _wasPlaying = currentValue.isPlaying; + shouldUpdate = true; + } + + if (currentValue.isInitialized != _wasInitialized) { + _wasInitialized = currentValue.isInitialized; + shouldUpdate = true; + } + + // Only call setState if there are relevant changes + if (shouldUpdate) { + setState(() {}); + } + } + @override void dispose() { + _controller?.removeListener(_onVideoPlayerValueChanged); _controller?.dispose(); super.dispose(); } @@ -131,20 +177,21 @@ class _AudioTracksDemoState extends State { // Video selection dropdown Padding( padding: const EdgeInsets.all(16.0), - child: DropdownButtonFormField( - value: _selectedVideoIndex, - decoration: const InputDecoration( - labelText: 'Select Video', + child: DropdownMenu( + initialSelection: _selectedVideoIndex, + label: const Text('Select Video'), + inputDecorationTheme: const InputDecorationTheme( border: OutlineInputBorder(), ), - items: - _sampleVideos.asMap().entries.map((MapEntry entry) { - return DropdownMenuItem( - value: entry.key, - child: Text('Video ${entry.key + 1}'), + dropdownMenuEntries: + _sampleVideos.indexed.map((record) { + final (index, _) = record; + return DropdownMenuEntry( + value: index, + label: 'Video ${index + 1}', ); }).toList(), - onChanged: (int? value) { + onSelected: (int? value) { if (value != null && value != _selectedVideoIndex) { setState(() { _selectedVideoIndex = value; @@ -233,7 +280,6 @@ class _AudioTracksDemoState extends State { } else { _controller!.play(); } - setState(() {}); }, icon: Icon( _controller!.value.isPlaying ? Icons.pause : Icons.play_arrow, diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 0b5776646033..afb6735bfecf 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -849,6 +849,12 @@ class VideoPlayerController extends ValueNotifier { throw Exception('VideoPlayerController is disposed or not initialized'); } await _videoPlayerPlatform.selectAudioTrack(_playerId, trackId); + + if (Platform.isAndroid) { + // Add a small delay to allow ExoPlayer to process the track selection change + // This is needed because ExoPlayer's track selection update is asynchronous + await Future.delayed(const Duration(milliseconds: 100)); + } } bool get _isDisposedOrNotInitialized => _isDisposed || !value.isInitialized; diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index 2290901a5c75..3dd8a67452bb 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,6 +1,6 @@ ## NEXT -* Implements `getAudioTracks()` and `selectAudioTrack()` methods for iOS/macOS using AVFoundation. +* Implements `getAudioTracks()` and `selectAudioTrack()` methods. * 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 56541bb52499..49507339020c 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m @@ -1155,9 +1155,10 @@ - (void)testGetAudioTracksWithMediaSelectionOptions { // Mock current selection for both iOS 11+ and older versions id mockCurrentMediaSelection = OCMClassMock([AVMediaSelection class]); OCMStub([mockPlayerItem currentMediaSelection]).andReturn(mockCurrentMediaSelection); - OCMStub([mockCurrentMediaSelection selectedMediaOptionInMediaSelectionGroup:mockMediaSelectionGroup]) + OCMStub( + [mockCurrentMediaSelection selectedMediaOptionInMediaSelectionGroup:mockMediaSelectionGroup]) .andReturn(mockOption1); - + // Also mock the deprecated method for iOS < 11 OCMStub([mockPlayerItem selectedMediaOptionInMediaSelectionGroup:mockMediaSelectionGroup]) .andReturn(mockOption1); 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 e095a5beef91..320103dab4bc 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 @@ -511,11 +511,12 @@ - (nullable FVPNativeAudioTrackData *)getAudioTracks:(FlutterError *_Nullable *_ } NSString *commonMetadataTitle = nil; - for (AVMetadataItem *item in option.commonMetadata) { - if ([item.commonKey isEqualToString:AVMetadataCommonKeyTitle] && item.stringValue) { - commonMetadataTitle = item.stringValue; - break; - } + NSArray *titleItems = + [AVMetadataItem metadataItemsFromArray:option.commonMetadata + withKey:AVMetadataCommonKeyTitle + keySpace:AVMetadataKeySpaceCommon]; + if (titleItems.count > 0 && titleItems.firstObject.stringValue) { + commonMetadataTitle = titleItems.firstObject.stringValue; } BOOL isSelected = (currentSelection == option) || [currentSelection isEqual:option]; @@ -567,59 +568,67 @@ - (nullable FVPNativeAudioTrackData *)getAudioTracks:(FlutterError *_Nullable *_ NSNumber *channelCount = nil; NSString *codec = nil; - // Only attempt format description parsing in production (non-test) environments - // Skip entirely if we detect any mock objects or test environment indicators - NSString *trackClassName = NSStringFromClass([track class]); - BOOL isTestEnvironment = [trackClassName containsString:@"OCMockObject"] || - [trackClassName containsString:@"Mock"] || - NSClassFromString(@"XCTestCase") != nil; - - if (track.formatDescriptions.count > 0 && !isTestEnvironment) { + // Attempt format description parsing + if (track.formatDescriptions.count > 0) { @try { id formatDescObj = track.formatDescriptions[0]; - NSString *className = NSStringFromClass([formatDescObj class]); - - // Additional safety: only process objects that are clearly Core Media format descriptions - if (formatDescObj && ([className hasPrefix:@"CMAudioFormatDescription"] || - [className hasPrefix:@"CMVideoFormatDescription"] || - [className hasPrefix:@"CMFormatDescription"])) { - CMFormatDescriptionRef formatDesc = (__bridge CMFormatDescriptionRef)formatDescObj; - - // Get audio stream basic description - const AudioStreamBasicDescription *audioDesc = - CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc); - if (audioDesc) { - if (audioDesc->mSampleRate > 0) { - sampleRate = @((NSInteger)audioDesc->mSampleRate); - } - if (audioDesc->mChannelsPerFrame > 0) { - channelCount = @(audioDesc->mChannelsPerFrame); - } - } - // Try to get codec information - FourCharCode codecType = CMFormatDescriptionGetMediaSubType(formatDesc); - switch (codecType) { - case kAudioFormatMPEG4AAC: - codec = @"aac"; - break; - case kAudioFormatAC3: - codec = @"ac3"; - break; - case kAudioFormatEnhancedAC3: - codec = @"eac3"; - break; - case kAudioFormatMPEGLayer3: - codec = @"mp3"; - break; - default: - codec = nil; - break; + // Validate that we have a valid format description object + if (formatDescObj && [formatDescObj respondsToSelector:@selector(self)]) { + NSString *className = NSStringFromClass([formatDescObj class]); + + // Only process objects that are clearly Core Media format descriptions + // This works for both real CMFormatDescription objects and properly configured mock + // objects + if ([className hasPrefix:@"CMAudioFormatDescription"] || + [className hasPrefix:@"CMVideoFormatDescription"] || + [className hasPrefix:@"CMFormatDescription"] || + [formatDescObj + isKindOfClass:[NSObject + class]]) { // Allow mock objects that inherit from NSObject + + CMFormatDescriptionRef formatDesc = (__bridge CMFormatDescriptionRef)formatDescObj; + + // Validate the format description reference before using Core Media APIs + if (formatDesc && CFGetTypeID(formatDesc) == CMFormatDescriptionGetTypeID()) { + // Get audio stream basic description + const AudioStreamBasicDescription *audioDesc = + CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc); + if (audioDesc) { + if (audioDesc->mSampleRate > 0) { + sampleRate = @((NSInteger)audioDesc->mSampleRate); + } + if (audioDesc->mChannelsPerFrame > 0) { + channelCount = @(audioDesc->mChannelsPerFrame); + } + } + + // Try to get codec information + FourCharCode codecType = CMFormatDescriptionGetMediaSubType(formatDesc); + switch (codecType) { + case kAudioFormatMPEG4AAC: + codec = @"aac"; + break; + case kAudioFormatAC3: + codec = @"ac3"; + break; + case kAudioFormatEnhancedAC3: + codec = @"eac3"; + break; + case kAudioFormatMPEGLayer3: + codec = @"mp3"; + break; + default: + codec = nil; + break; + } + } } } } @catch (NSException *exception) { - // Silently handle any exceptions from format description parsing - // This can happen with mock objects in tests or invalid format descriptions + // Handle any exceptions from format description parsing gracefully + // This ensures the method continues to work even with mock objects or invalid data + // In tests, this allows the method to return track data with nil format fields } } diff --git a/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart b/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart index e1db949a29c1..f2c2fffb82f3 100644 --- a/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart +++ b/packages/video_player/video_player_web/example/integration_test/pkg_web_tweaks.dart @@ -57,8 +57,7 @@ extension type Descriptor._(JSObject _) implements JSObject { factory Descriptor.accessor({ void Function(JSAny? value)? set, JSAny? Function()? get, - }) => - Descriptor._accessor(set: set?.toJS, get: get?.toJS); + }) => Descriptor._accessor(set: set?.toJS, get: get?.toJS); external factory Descriptor._accessor({ // JSBoolean configurable, diff --git a/packages/video_player/video_player_web/example/integration_test/video_player_test.dart b/packages/video_player/video_player_web/example/integration_test/video_player_test.dart index db36143f6f67..78c85c99d1b2 100644 --- a/packages/video_player/video_player_web/example/integration_test/video_player_test.dart +++ b/packages/video_player/video_player_web/example/integration_test/video_player_test.dart @@ -24,17 +24,19 @@ void main() { setUp(() { // Never set "src" on the video, so this test doesn't hit the network! - video = web.HTMLVideoElement() - ..controls = true - ..playsInline = false; + video = + web.HTMLVideoElement() + ..controls = true + ..playsInline = false; }); testWidgets('initialize() calls load', (WidgetTester _) async { bool loadCalled = false; - video['load'] = () { - loadCalled = true; - }.toJS; + video['load'] = + () { + loadCalled = true; + }.toJS; VideoPlayer(videoElement: video).initialize(); @@ -191,15 +193,17 @@ void main() { WidgetTester tester, ) async { // Take all the "buffering" events that we see during the next few seconds - final Future> stream = timedStream - .where( - (VideoEvent event) => bufferingEvents.contains(event.eventType), - ) - .map( - (VideoEvent event) => - event.eventType == VideoEventType.bufferingStart, - ) - .toList(); + final Future> stream = + timedStream + .where( + (VideoEvent event) => + bufferingEvents.contains(event.eventType), + ) + .map( + (VideoEvent event) => + event.eventType == VideoEventType.bufferingStart, + ) + .toList(); // Simulate some events coming from the player... player.setBuffering(true); @@ -222,15 +226,17 @@ void main() { WidgetTester tester, ) async { // Take all the "buffering" events that we see during the next few seconds - final Future> stream = timedStream - .where( - (VideoEvent event) => bufferingEvents.contains(event.eventType), - ) - .map( - (VideoEvent event) => - event.eventType == VideoEventType.bufferingStart, - ) - .toList(); + final Future> stream = + timedStream + .where( + (VideoEvent event) => + bufferingEvents.contains(event.eventType), + ) + .map( + (VideoEvent event) => + event.eventType == VideoEventType.bufferingStart, + ) + .toList(); player.setBuffering(true); @@ -247,15 +253,17 @@ void main() { WidgetTester tester, ) async { // Take all the "buffering" events that we see during the next few seconds - final Future> stream = timedStream - .where( - (VideoEvent event) => bufferingEvents.contains(event.eventType), - ) - .map( - (VideoEvent event) => - event.eventType == VideoEventType.bufferingStart, - ) - .toList(); + final Future> stream = + timedStream + .where( + (VideoEvent event) => + bufferingEvents.contains(event.eventType), + ) + .map( + (VideoEvent event) => + event.eventType == VideoEventType.bufferingStart, + ) + .toList(); player.setBuffering(true); @@ -277,12 +285,13 @@ void main() { video.dispatchEvent(web.Event('canplay')); // Take all the "initialized" events that we see during the next few seconds - final Future> stream = timedStream - .where( - (VideoEvent event) => - event.eventType == VideoEventType.initialized, - ) - .toList(); + final Future> stream = + timedStream + .where( + (VideoEvent event) => + event.eventType == VideoEventType.initialized, + ) + .toList(); video.dispatchEvent(web.Event('canplay')); video.dispatchEvent(web.Event('canplay')); @@ -300,12 +309,13 @@ void main() { video.dispatchEvent(web.Event('loadedmetadata')); video.dispatchEvent(web.Event('loadedmetadata')); - final Future> stream = timedStream - .where( - (VideoEvent event) => - event.eventType == VideoEventType.initialized, - ) - .toList(); + final Future> stream = + timedStream + .where( + (VideoEvent event) => + event.eventType == VideoEventType.initialized, + ) + .toList(); final List events = await stream; @@ -318,12 +328,13 @@ void main() { video.dispatchEvent(web.Event('loadeddata')); video.dispatchEvent(web.Event('loadeddata')); - final Future> stream = timedStream - .where( - (VideoEvent event) => - event.eventType == VideoEventType.initialized, - ) - .toList(); + final Future> stream = + timedStream + .where( + (VideoEvent event) => + event.eventType == VideoEventType.initialized, + ) + .toList(); final List events = await stream; @@ -335,12 +346,13 @@ void main() { setInfinityDuration(video); expect(video.duration.isInfinite, isTrue); - final Future> stream = timedStream - .where( - (VideoEvent event) => - event.eventType == VideoEventType.initialized, - ) - .toList(); + final Future> stream = + timedStream + .where( + (VideoEvent event) => + event.eventType == VideoEventType.initialized, + ) + .toList(); video.dispatchEvent(web.Event('canplay')); diff --git a/packages/video_player/video_player_web/example/integration_test/video_player_web_test.dart b/packages/video_player/video_player_web/example/integration_test/video_player_web_test.dart index 856188091096..9a2cd8c8e85d 100644 --- a/packages/video_player/video_player_web/example/integration_test/video_player_web_test.dart +++ b/packages/video_player/video_player_web/example/integration_test/video_player_web_test.dart @@ -124,19 +124,19 @@ void main() { ) async { final int videoPlayerId = (await VideoPlayerPlatform.instance.createWithOptions( - VideoCreationOptions( - dataSource: DataSource( - sourceType: DataSourceType.network, - uri: getUrlForAssetAsNetworkSource( - 'assets/__non_existent.webm', + VideoCreationOptions( + dataSource: DataSource( + sourceType: DataSourceType.network, + uri: getUrlForAssetAsNetworkSource( + 'assets/__non_existent.webm', + ), + ), + viewType: VideoViewType.platformView, ), - ), - viewType: VideoViewType.platformView, - ), - ))!; + ))!; - final Stream eventStream = - VideoPlayerPlatform.instance.videoEventsFor(videoPlayerId); + final Stream eventStream = VideoPlayerPlatform.instance + .videoEventsFor(videoPlayerId); // Mute video to allow autoplay (See https://goo.gl/xX8pDD) await VideoPlayerPlatform.instance.setVolume(videoPlayerId, 0); @@ -207,15 +207,18 @@ void main() { 'double call to play will emit a single isPlayingStateUpdate event', (WidgetTester tester) async { final int videoPlayerId = await playerId; - final Stream eventStream = - VideoPlayerPlatform.instance.videoEventsFor(videoPlayerId); - - final Future> stream = eventStream.timeout( - const Duration(seconds: 2), - onTimeout: (EventSink sink) { - sink.close(); - }, - ).toList(); + final Stream eventStream = VideoPlayerPlatform.instance + .videoEventsFor(videoPlayerId); + + final Future> stream = + eventStream + .timeout( + const Duration(seconds: 2), + onTimeout: (EventSink sink) { + sink.close(); + }, + ) + .toList(); await VideoPlayerPlatform.instance.setVolume(videoPlayerId, 0); await VideoPlayerPlatform.instance.play(videoPlayerId); @@ -247,15 +250,18 @@ void main() { 'video playback lifecycle', (WidgetTester tester) async { final int videoPlayerId = await playerId; - final Stream eventStream = - VideoPlayerPlatform.instance.videoEventsFor(videoPlayerId); - - final Future> stream = eventStream.timeout( - const Duration(seconds: 2), - onTimeout: (EventSink sink) { - sink.close(); - }, - ).toList(); + final Stream eventStream = VideoPlayerPlatform.instance + .videoEventsFor(videoPlayerId); + + final Future> stream = + eventStream + .timeout( + const Duration(seconds: 2), + onTimeout: (EventSink sink) { + sink.close(); + }, + ) + .toList(); await VideoPlayerPlatform.instance.setVolume(videoPlayerId, 0); await VideoPlayerPlatform.instance.play(videoPlayerId); diff --git a/packages/video_player/video_player_web/example/pubspec.yaml b/packages/video_player/video_player_web/example/pubspec.yaml index c11ee9cfc8bb..e3bce6949901 100644 --- a/packages/video_player/video_player_web/example/pubspec.yaml +++ b/packages/video_player/video_player_web/example/pubspec.yaml @@ -18,7 +18,3 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins -dependency_overrides: - video_player_platform_interface: {path: ../../../../packages/video_player/video_player_platform_interface} diff --git a/packages/video_player/video_player_web/lib/src/video_player.dart b/packages/video_player/video_player_web/lib/src/video_player.dart index fa314c8698fe..3791fe5395a1 100644 --- a/packages/video_player/video_player_web/lib/src/video_player.dart +++ b/packages/video_player/video_player_web/lib/src/video_player.dart @@ -42,8 +42,8 @@ class VideoPlayer { VideoPlayer({ required web.HTMLVideoElement videoElement, @visibleForTesting StreamController? eventController, - }) : _videoElement = videoElement, - _eventController = eventController ?? StreamController(); + }) : _videoElement = videoElement, + _eventController = eventController ?? StreamController(); final StreamController _eventController; final web.HTMLVideoElement _videoElement; @@ -313,12 +313,13 @@ class VideoPlayer { _videoElement.duration, ); - final Size? size = _videoElement.videoHeight.isFinite - ? Size( - _videoElement.videoWidth.toDouble(), - _videoElement.videoHeight.toDouble(), - ) - : null; + final Size? size = + _videoElement.videoHeight.isFinite + ? Size( + _videoElement.videoWidth.toDouble(), + _videoElement.videoHeight.toDouble(), + ) + : null; _eventController.add( VideoEvent( @@ -339,9 +340,10 @@ class VideoPlayer { _isBuffering = buffering; _eventController.add( VideoEvent( - eventType: _isBuffering - ? VideoEventType.bufferingStart - : VideoEventType.bufferingEnd, + eventType: + _isBuffering + ? VideoEventType.bufferingStart + : VideoEventType.bufferingEnd, ), ); } diff --git a/packages/video_player/video_player_web/lib/video_player_web.dart b/packages/video_player/video_player_web/lib/video_player_web.dart index cbcf20b95bf8..5fdc71a8db56 100644 --- a/packages/video_player/video_player_web/lib/video_player_web.dart +++ b/packages/video_player/video_player_web/lib/video_player_web.dart @@ -90,11 +90,12 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { ); } - final web.HTMLVideoElement videoElement = web.HTMLVideoElement() - ..id = 'videoElement-$playerId' - ..style.border = 'none' - ..style.height = '100%' - ..style.width = '100%'; + final web.HTMLVideoElement videoElement = + web.HTMLVideoElement() + ..id = 'videoElement-$playerId' + ..style.border = 'none' + ..style.height = '100%' + ..style.width = '100%'; // TODO(hterkelsen): Use initialization parameters once they are available ui_web.platformViewRegistry.registerViewFactory( diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index d8ea9bd434ba..ca36ffe35ee6 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -31,7 +31,3 @@ dev_dependencies: topics: - video - video-player -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins -dependency_overrides: - video_player_platform_interface: {path: ../../../packages/video_player/video_player_platform_interface}