From c40d4bf51b82c10063a4feee50b2f2464d2457fc Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <178714+mx1up@users.noreply.github.com> Date: Wed, 30 Apr 2025 21:52:37 +0200 Subject: [PATCH 01/10] [video_player] add seekToDefaultPosition to api and implement for android --- .../video_player/lib/video_player.dart | 14 ++++++++ .../flutter/plugins/videoplayer/Messages.java | 30 ++++++++++++++-- .../plugins/videoplayer/VideoPlayer.java | 4 +++ .../videoplayer/VideoPlayerPlugin.java | 6 ++++ .../lib/src/android_video_player.dart | 5 +++ .../lib/src/messages.g.dart | 26 +++++++++++++- .../pigeons/messages.dart | 1 + .../video_player_android/test/test_api.g.dart | 36 ++++++++++++++++++- .../lib/video_player_platform_interface.dart | 10 ++++++ 9 files changed, 128 insertions(+), 4 deletions(-) diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 12b30c2a146..5241e1f7ba2 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -660,6 +660,20 @@ class VideoPlayerController extends ValueNotifier { _updatePosition(position); } + /// Seeks to the default position associated with the current MediaItem. + /// + /// The position can depend on the type of media being played. + /// For live streams it will typically be the live edge. + /// For other streams it will typically be the start. + Future seekToDefaultPosition() async { + if (_isDisposedOrNotInitialized) { + return; + } + await _videoPlayerPlatform.seekToDefaultPosition(_textureId); + final position = await _videoPlayerPlatform.getPosition(_textureId); + _updatePosition(position); + } + /// Sets the audio volume of [this]. /// /// [volume] indicates a value between 0.0 (silent) and 1.0 (full volume) on a diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java index 581cb7b80f6..5d25a524333 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.videoplayer; @@ -393,6 +393,8 @@ public interface AndroidVideoPlayerApi { void seekTo(@NonNull Long playerId, @NonNull Long position); + void seekToDefaultPosition(@NonNull Long playerId); + void pause(@NonNull Long playerId); void setMixWithOthers(@NonNull Boolean mixWithOthers); @@ -401,7 +403,6 @@ public interface AndroidVideoPlayerApi { static @NonNull MessageCodec getCodec() { return PigeonCodec.INSTANCE; } - /** * Sets up an instance of `AndroidVideoPlayerApi` to handle messages through the * `binaryMessenger`. @@ -643,6 +644,31 @@ static void setUp( channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekToDefaultPosition" + + messageChannelSuffix, + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + Long playerIdArg = (Long) args.get(0); + try { + api.seekToDefaultPosition(playerIdArg); + wrapped.add(0, null); + } catch (Throwable exception) { + wrapped = wrapError(exception); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java index 8040e86419c..5659bdad767 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java @@ -104,6 +104,10 @@ void seekTo(int location) { exoPlayer.seekTo(location); } + void seekToDefaultPosition() { + exoPlayer.seekToDefaultPosition(); + } + long getPosition() { return exoPlayer.getCurrentPosition(); } diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java index 3db5fd42a26..31a18e47b4f 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java @@ -212,6 +212,12 @@ public void seekTo(@NonNull Long playerId, @NonNull Long position) { player.seekTo(position.intValue()); } + @Override + public void seekToDefaultPosition(@NonNull Long playerId) { + VideoPlayer player = getPlayer(playerId); + player.seekToDefaultPosition(); + } + @Override public void pause(@NonNull Long playerId) { VideoPlayer player = getPlayer(playerId); diff --git a/packages/video_player/video_player_android/lib/src/android_video_player.dart b/packages/video_player/video_player_android/lib/src/android_video_player.dart index 2df0a0ebc36..80faa69d0de 100644 --- a/packages/video_player/video_player_android/lib/src/android_video_player.dart +++ b/packages/video_player/video_player_android/lib/src/android_video_player.dart @@ -124,6 +124,11 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { return _api.seekTo(playerId, position.inMilliseconds); } + @override + Future seekToDefaultPosition(int playerId) { + return _api.seekToDefaultPosition(playerId); + } + @override Future getPosition(int playerId) async { final int position = await _api.position(playerId); diff --git a/packages/video_player/video_player_android/lib/src/messages.g.dart b/packages/video_player/video_player_android/lib/src/messages.g.dart index 717e8e020d0..1ae1cf0d8b0 100644 --- a/packages/video_player/video_player_android/lib/src/messages.g.dart +++ b/packages/video_player/video_player_android/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -382,6 +382,30 @@ class AndroidVideoPlayerApi { } } + Future seekToDefaultPosition(int playerId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekToDefaultPosition$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([playerId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + Future pause(int playerId) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.pause$pigeonVar_messageChannelSuffix'; diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart index a1f1211e533..529108e81bb 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -50,6 +50,7 @@ abstract class AndroidVideoPlayerApi { void play(int playerId); int position(int playerId); void seekTo(int playerId, int position); + void seekToDefaultPosition(int playerId); void pause(int playerId); void setMixWithOthers(bool mixWithOthers); } diff --git a/packages/video_player/video_player_android/test/test_api.g.dart b/packages/video_player/video_player_android/test/test_api.g.dart index f3ac1b7fb73..bd4ad40a121 100644 --- a/packages/video_player/video_player_android/test/test_api.g.dart +++ b/packages/video_player/video_player_android/test/test_api.g.dart @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: avoid_relative_lib_imports @@ -73,6 +73,8 @@ abstract class TestHostVideoPlayerApi { void seekTo(int playerId, int position); + void seekToDefaultPosition(int playerId); + void pause(int playerId); void setMixWithOthers(bool mixWithOthers); @@ -378,6 +380,38 @@ abstract class TestHostVideoPlayerApi { }); } } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekToDefaultPosition$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekToDefaultPosition was null.'); + final List args = (message as List?)!; + final int? arg_playerId = (args[0] as int?); + assert(arg_playerId != null, + 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekToDefaultPosition was null, expected non-null int.'); + try { + api.seekToDefaultPosition(arg_playerId!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } { final BasicMessageChannel< Object?> pigeonVar_channel = BasicMessageChannel< diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart index 13c9cf55aa7..f26743417ac 100644 --- a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart +++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart @@ -90,6 +90,16 @@ abstract class VideoPlayerPlatform extends PlatformInterface { throw UnimplementedError('seekTo() has not been implemented.'); } + /// Seeks to the default position associated with the current MediaItem. + /// + /// The position can depend on the type of media being played. + /// For live streams it will typically be the live edge. + /// For other streams it will typically be the start. + Future seekToDefaultPosition(int playerId) { + throw UnimplementedError( + 'seekToDefaultPosition() has not been implemented.'); + } + /// Sets the playback speed to a [speed] value indicating the playback rate. Future setPlaybackSpeed(int playerId, double speed) { throw UnimplementedError('setPlaybackSpeed() has not been implemented.'); From 800fbe269184e46dd061889696859dd4dcf04e77 Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <178714+mx1up@users.noreply.github.com> Date: Wed, 30 Apr 2025 21:53:12 +0200 Subject: [PATCH 02/10] [video_player] temporarily override deps to try out new functionality --- packages/video_player/video_player/pubspec.yaml | 4 ++++ .../video_player/video_player_android/example/pubspec.yaml | 3 ++- packages/video_player/video_player_android/pubspec.yaml | 4 ++++ packages/video_player/video_player_avfoundation/pubspec.yaml | 4 ++++ packages/video_player/video_player_web/pubspec.yaml | 4 ++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 0f80d59e6a9..2ca4c9b7b47 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -21,6 +21,10 @@ flutter: web: default_package: video_player_web +dependency_overrides: + video_player_platform_interface: + path: ../video_player_platform_interface + dependencies: flutter: sdk: flutter diff --git a/packages/video_player/video_player_android/example/pubspec.yaml b/packages/video_player/video_player_android/example/pubspec.yaml index a08ae689747..753f43fef4b 100644 --- a/packages/video_player/video_player_android/example/pubspec.yaml +++ b/packages/video_player/video_player_android/example/pubspec.yaml @@ -18,7 +18,8 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - video_player_platform_interface: ^6.3.0 + video_player_platform_interface: + path: ../../video_player_platform_interface dev_dependencies: espresso: ^0.4.0 diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 40b0c33388c..e9078a221fc 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -17,6 +17,10 @@ flutter: package: io.flutter.plugins.videoplayer pluginClass: VideoPlayerPlugin +dependency_overrides: + video_player_platform_interface: + path: ../video_player_platform_interface + dependencies: flutter: sdk: flutter diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index 20087db42cc..8098ff78c63 100644 --- a/packages/video_player/video_player_avfoundation/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/pubspec.yaml @@ -21,6 +21,10 @@ flutter: pluginClass: FVPVideoPlayerPlugin sharedDarwinSource: true +dependency_overrides: + video_player_platform_interface: + path: ../video_player_platform_interface + dependencies: flutter: sdk: flutter diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index 67c7628130b..449a1c4f39a 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -16,6 +16,10 @@ flutter: pluginClass: VideoPlayerPlugin fileName: video_player_web.dart +dependency_overrides: + video_player_platform_interface: + path: ../video_player_platform_interface + dependencies: flutter: sdk: flutter From 06bdd4a43a908efb7b6256c0f2a4705cdc12b1ef Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <178714+mx1up@users.noreply.github.com> Date: Fri, 2 May 2025 22:42:09 +0200 Subject: [PATCH 03/10] [video_player] seekToDefaultPosition ios implementation --- .../FVPVideoPlayer.m | 11 ++++++ .../FVPVideoPlayerPlugin.m | 10 ++++++ .../FVPVideoPlayer.h | 7 ++++ .../video_player_avfoundation/messages.g.h | 4 ++- .../video_player_avfoundation/messages.g.m | 28 ++++++++++++++- .../macos/Runner.xcodeproj/project.pbxproj | 18 ++++++++++ .../example/pubspec.yaml | 4 +++ .../lib/src/avfoundation_video_player.dart | 5 +++ .../lib/src/messages.g.dart | 26 +++++++++++++- .../pigeons/messages.dart | 3 ++ .../test/avfoundation_video_player_test.dart | 6 ++++ .../test/test_api.g.dart | 36 ++++++++++++++++++- 12 files changed, 154 insertions(+), 4 deletions(-) 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 04d6d21cb2e..e7787e71172 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 @@ -443,6 +443,17 @@ - (void)seekTo:(int64_t)location completionHandler:(void (^)(BOOL))completionHan }]; } +- (void)seekToDefaultPosition:(void (^)(BOOL))completionHandler { + AVPlayerItem *item = _player.currentItem; + if (item.seekableTimeRanges.count > 0) { + CMTimeRange seekableRange = [item.seekableTimeRanges.lastObject CMTimeRangeValue]; + CMTime liveEdgeTime = CMTimeRangeGetEnd(seekableRange); + [self seekTo:liveEdgeTime.value completionHandler:completionHandler]; + } else { + [self seekTo:0 completionHandler:completionHandler]; + } +} + - (void)setIsLooping:(BOOL)isLooping { _isLooping = isLooping; } diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m index 1936435ffd0..244e9844060 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m @@ -302,6 +302,16 @@ - (void)seekTo:(NSInteger)position }]; } +- (void)seekToDefaultPositionForPlayer:(NSInteger)playerIdentifier + completion:(nonnull void (^)(FlutterError *_Nullable))completion { + FVPVideoPlayer *player = self.playersByIdentifier[@(playerIdentifier)]; + [player seekToDefaultPosition:^(BOOL finished) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil); + }); + }]; +} + - (void)pausePlayer:(NSInteger)playerIdentifier error:(FlutterError **)error { FVPVideoPlayer *player = self.playersByIdentifier[@(playerIdentifier)]; [player pause]; diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer.h index d668c820065..eeb19d140a4 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer.h @@ -66,6 +66,13 @@ NS_ASSUME_NONNULL_BEGIN /// Seeks to the specified location in the video and calls the completion handler when done, if one /// is supplied. - (void)seekTo:(int64_t)location completionHandler:(void (^_Nullable)(BOOL))completionHandler; + +/// Seeks to the default position associated with the current item. +/// +/// The position can depend on the type of media being played. +/// For live streams it will typically be the live edge. +/// For other streams it will typically be the start. +- (void)seekToDefaultPosition:(void (^_Nullable)(BOOL))completionHandler; @end NS_ASSUME_NONNULL_END diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h index 6a115c6b9ac..20ef2bdebc4 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -78,6 +78,8 @@ NSObject *FVPGetMessagesCodec(void); - (void)seekTo:(NSInteger)position forPlayer:(NSInteger)playerId completion:(void (^)(FlutterError *_Nullable))completion; +- (void)seekToDefaultPositionForPlayer:(NSInteger)playerId + completion:(void (^)(FlutterError *_Nullable))completion; - (void)pausePlayer:(NSInteger)playerId error:(FlutterError *_Nullable *_Nonnull)error; - (void)setMixWithOthers:(BOOL)mixWithOthers error:(FlutterError *_Nullable *_Nonnull)error; @end diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m index 83c41211f74..9b78f142d67 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "./include/video_player_avfoundation/messages.g.h" @@ -410,6 +410,32 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString + stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.video_player_avfoundation." + @"AVFoundationVideoPlayerApi.seekToDefaultPosition", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FVPGetMessagesCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(seekToDefaultPositionForPlayer:completion:)], + @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " + @"@selector(seekToDefaultPositionForPlayer:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSInteger arg_playerId = [GetNullableObjectAtIndex(args, 0) integerValue]; + [api seekToDefaultPositionForPlayer:arg_playerId + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:[NSString stringWithFormat:@"%@%@", diff --git a/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj index 3c4eb3a512b..1b0ac54f771 100644 --- a/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/video_player/video_player_avfoundation/example/macos/Runner.xcodeproj/project.pbxproj @@ -246,6 +246,7 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + A813AD2BF8A041ACDE582FFB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -395,6 +396,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + A813AD2BF8A041ACDE582FFB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; D3E396DFBCC51886820113AA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/packages/video_player/video_player_avfoundation/example/pubspec.yaml b/packages/video_player/video_player_avfoundation/example/pubspec.yaml index 9441576e59d..4bf1cf401e4 100644 --- a/packages/video_player/video_player_avfoundation/example/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/example/pubspec.yaml @@ -6,6 +6,10 @@ environment: sdk: ^3.4.0 flutter: ">=3.22.0" +dependency_overrides: + video_player_platform_interface: + path: ../../video_player_platform_interface + dependencies: flutter: sdk: flutter diff --git a/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart b/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart index ec790187abf..a51a6e6fc80 100644 --- a/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart +++ b/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart @@ -128,6 +128,11 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { return _api.seekTo(position.inMilliseconds, playerId); } + @override + Future seekToDefaultPosition(int playerId) { + return _api.seekToDefaultPosition(playerId); + } + @override Future getPosition(int playerId) async { final int position = await _api.getPosition(playerId); diff --git a/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart b/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart index c1d16ecce5e..cf7361dbd16 100644 --- a/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart +++ b/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -382,6 +382,30 @@ class AVFoundationVideoPlayerApi { } } + Future seekToDefaultPosition(int playerId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.seekToDefaultPosition$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final List? pigeonVar_replyList = + await pigeonVar_channel.send([playerId]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + Future pause(int playerId) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.pause$pigeonVar_messageChannelSuffix'; diff --git a/packages/video_player/video_player_avfoundation/pigeons/messages.dart b/packages/video_player/video_player_avfoundation/pigeons/messages.dart index ffa2f968c32..70b0fe86f4a 100644 --- a/packages/video_player/video_player_avfoundation/pigeons/messages.dart +++ b/packages/video_player/video_player_avfoundation/pigeons/messages.dart @@ -69,6 +69,9 @@ abstract class AVFoundationVideoPlayerApi { @async @ObjCSelector('seekTo:forPlayer:') void seekTo(int position, int playerId); + @async + @ObjCSelector('seekToDefaultPositionForPlayer:') + void seekToDefaultPosition(int playerId); @ObjCSelector('pausePlayer:') void pause(int playerId); @ObjCSelector('setMixWithOthers:') diff --git a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart index 3d02cef3cbe..a2dc924a9ff 100644 --- a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart +++ b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart @@ -71,6 +71,12 @@ class _ApiLogger implements TestHostVideoPlayerApi { this.playerId = playerId; } + @override + Future seekToDefaultPosition(int playerId) async { + log.add('seekToDefaultPosition'); + this.playerId = playerId; + } + @override void setLooping(bool loop, int playerId) { log.add('setLooping'); diff --git a/packages/video_player/video_player_avfoundation/test/test_api.g.dart b/packages/video_player/video_player_avfoundation/test/test_api.g.dart index 38ed42f1f9e..b7dde442298 100644 --- a/packages/video_player/video_player_avfoundation/test/test_api.g.dart +++ b/packages/video_player/video_player_avfoundation/test/test_api.g.dart @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: avoid_relative_lib_imports @@ -73,6 +73,8 @@ abstract class TestHostVideoPlayerApi { Future seekTo(int position, int playerId); + Future seekToDefaultPosition(int playerId); + void pause(int playerId); void setMixWithOthers(bool mixWithOthers); @@ -379,6 +381,38 @@ abstract class TestHostVideoPlayerApi { }); } } + { + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.seekToDefaultPosition$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.seekToDefaultPosition was null.'); + final List args = (message as List?)!; + final int? arg_playerId = (args[0] as int?); + assert(arg_playerId != null, + 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.seekToDefaultPosition was null, expected non-null int.'); + try { + await api.seekToDefaultPosition(arg_playerId!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } { final BasicMessageChannel< Object?> pigeonVar_channel = BasicMessageChannel< From 7b3a0ed9364ce3b59d22f4a1f7d1a2647704a3c7 Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <178714+mx1up@users.noreply.github.com> Date: Sat, 3 May 2025 18:31:57 +0200 Subject: [PATCH 04/10] [video_player] add web implementation (untested) --- .../lib/src/video_player.dart | 19 +++++++++++++++++++ .../lib/video_player_web.dart | 5 +++++ 2 files changed, 24 insertions(+) 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 15639079c3b..7d81d9c400f 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 @@ -226,6 +226,25 @@ class VideoPlayer { _videoElement.currentTime = position.inMilliseconds.toDouble() / 1000; } + /// Seeks to the default position associated with the current MediaItem. + /// + /// The position can depend on the type of media being played. + /// For live streams it will typically be the live edge. + /// For other streams it will typically be the start. + void seekToDefaultPosition() { + // Seek to the end of the video if it is a live stream. + if (_videoElement.seekable.length == 0) { + // not a live stream, so seek to the start + seekTo(Duration.zero); + return; + } + + // Seek to the end of the video if it is a live stream. + final double liveEdgeTime = + _videoElement.seekable.end(_videoElement.seekable.length - 1); + seekTo(Duration(milliseconds: (liveEdgeTime * 1000).round())); + } + /// Returns the current playback head position as a [Duration]. Duration getPosition() { _sendBufferingRangesUpdate(); 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 d65eabacfcc..ab4b325f7cc 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 @@ -134,6 +134,11 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { return _player(playerId).seekTo(position); } + @override + Future seekToDefaultPosition(int playerId) async { + return _player(playerId).seekToDefaultPosition(); + } + @override Future getPosition(int playerId) async { return _player(playerId).getPosition(); From c0262cd2102885c517b176ecaf138984bdcd114f Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <178714+mx1up@users.noreply.github.com> Date: Sat, 3 May 2025 18:43:35 +0200 Subject: [PATCH 05/10] [video_player] extend FakeController --- packages/video_player/video_player/test/video_player_test.dart | 3 +++ 1 file changed, 3 insertions(+) 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 a7b0888faa6..6aa1d2d9340 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -50,6 +50,9 @@ class FakeController extends ValueNotifier @override Future seekTo(Duration moment) async {} + @override + Future seekToDefaultPosition() async {} + @override Future setVolume(double volume) async {} From 416161f15a58879ae5f52bf52f17336e5f41291d Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <178714+mx1up@users.noreply.github.com> Date: Sat, 3 May 2025 18:43:55 +0200 Subject: [PATCH 06/10] [video_player] add HLS live stream tab --- .../video_player/example/lib/main.dart | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/packages/video_player/video_player/example/lib/main.dart b/packages/video_player/video_player/example/lib/main.dart index 0ce77d603b5..941dd608be8 100644 --- a/packages/video_player/video_player/example/lib/main.dart +++ b/packages/video_player/video_player/example/lib/main.dart @@ -23,7 +23,7 @@ class _App extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( - length: 3, + length: 4, child: Scaffold( key: const ValueKey('home_page'), appBar: AppBar( @@ -51,6 +51,7 @@ class _App extends StatelessWidget { ), Tab(icon: Icon(Icons.insert_drive_file), text: 'Asset'), Tab(icon: Icon(Icons.list), text: 'List example'), + Tab(icon: Icon(Icons.stream), text: 'HLS live'), ], ), ), @@ -59,6 +60,7 @@ class _App extends StatelessWidget { _BumbleBeeRemoteVideo(), _ButterFlyAssetVideo(), _ButterFlyAssetVideoInList(), + _HLSVideo(), ], ), ), @@ -271,6 +273,66 @@ class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { } } +class _HLSVideo extends StatefulWidget { + @override + _HLSVideoState createState() => _HLSVideoState(); +} + +class _HLSVideoState extends State<_HLSVideo> { + late VideoPlayerController _controller; + + @override + void initState() { + super.initState(); + _controller = VideoPlayerController.networkUrl( + Uri.parse('https://ireplay.tv/test/blender.m3u8'), + videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), + ); + + _controller.addListener(() { + setState(() {}); + }); + _controller.setLooping(true); + _controller.initialize(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + Container(padding: const EdgeInsets.only(top: 20.0)), + const Text('With HLS live stream'), + Container( + padding: const EdgeInsets.all(20), + child: AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(_controller), + ClosedCaption(text: _controller.value.caption.text), + _ControlsOverlay(controller: _controller), + VideoProgressIndicator(_controller, allowScrubbing: true), + ], + ), + ), + ), + TextButton( + onPressed: () => _controller.seekToDefaultPosition(), + child: const Text('Seek to default pos')), + ], + ), + ); + } +} + class _ControlsOverlay extends StatelessWidget { const _ControlsOverlay({required this.controller}); From 9ad17f7933720d633dc19c088b634681efd3ca56 Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <178714+mx1up@users.noreply.github.com> Date: Sat, 3 May 2025 18:44:20 +0200 Subject: [PATCH 07/10] [video_player] temporarily override --- .../video_player/video_player/example/pubspec.yaml | 10 ++++++++++ packages/video_player/video_player/pubspec.yaml | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/packages/video_player/video_player/example/pubspec.yaml b/packages/video_player/video_player/example/pubspec.yaml index 5755db029dd..a6d1d148209 100644 --- a/packages/video_player/video_player/example/pubspec.yaml +++ b/packages/video_player/video_player/example/pubspec.yaml @@ -6,6 +6,16 @@ environment: sdk: ^3.4.0 flutter: ">=3.22.0" +dependency_overrides: + video_player_android: + path: ../../video_player_android + video_player_avfoundation: + path: ../../video_player_avfoundation + video_player_web: + path: ../../video_player_web + video_player_platform_interface: + path: ../../video_player_platform_interface + dependencies: flutter: sdk: flutter diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 2ca4c9b7b47..188be61e67b 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -22,6 +22,12 @@ flutter: default_package: video_player_web dependency_overrides: + video_player_android: + path: ../video_player_android + video_player_avfoundation: + path: ../video_player_avfoundation + video_player_web: + path: ../video_player_web video_player_platform_interface: path: ../video_player_platform_interface From 86169f5039bff7eff6f4b13618694a85965cd14a Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <178714+mx1up@users.noreply.github.com> Date: Sat, 3 May 2025 18:44:38 +0200 Subject: [PATCH 08/10] [video_player] automatic changes due to macos sample build --- .../macos/Runner.xcodeproj/project.pbxproj | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/video_player/video_player/example/macos/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player/example/macos/Runner.xcodeproj/project.pbxproj index e6fa40d2ed6..85062c3aab0 100644 --- a/packages/video_player/video_player/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/video_player/video_player/example/macos/Runner.xcodeproj/project.pbxproj @@ -193,6 +193,7 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + D341BFD5EA4254315E3CCE6E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -306,6 +307,23 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + D341BFD5EA4254315E3CCE6E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; D3E396DFBCC51886820113AA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; From 049acc67e7b4adbe15e479f494bd189fb8ca18fd Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <178714+mx1up@users.noreply.github.com> Date: Sat, 3 May 2025 19:22:18 +0200 Subject: [PATCH 09/10] [video_player] make ci happier --- .../video_player/video_player_android/example/pubspec.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/video_player/video_player_android/example/pubspec.yaml b/packages/video_player/video_player_android/example/pubspec.yaml index 753f43fef4b..04f84035a15 100644 --- a/packages/video_player/video_player_android/example/pubspec.yaml +++ b/packages/video_player/video_player_android/example/pubspec.yaml @@ -6,6 +6,12 @@ environment: sdk: ^3.5.0 flutter: ">=3.24.0" +dependency_overrides: + video_player_android: + path: ../../video_player_android + video_player_platform_interface: + path: ../../video_player_platform_interface + dependencies: flutter: sdk: flutter From 923a22faaa46aca23cf108fa7ba684e223723179 Mon Sep 17 00:00:00 2001 From: matthias sweertvaegher <178714+mx1up@users.noreply.github.com> Date: Sat, 3 May 2025 19:29:32 +0200 Subject: [PATCH 10/10] [video_player_android] add seekToDefaultPosition to test api --- .../test/android_video_player_test.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/video_player/video_player_android/test/android_video_player_test.dart b/packages/video_player/video_player_android/test/android_video_player_test.dart index ddc6617fc4e..fa4b74d87e6 100644 --- a/packages/video_player/video_player_android/test/android_video_player_test.dart +++ b/packages/video_player/video_player_android/test/android_video_player_test.dart @@ -72,6 +72,12 @@ class _ApiLogger implements TestHostVideoPlayerApi { passedPosition = position; } + @override + void seekToDefaultPosition(int playerId) { + log.add('seekToDefaultPosition'); + passedPlayerId = playerId; + } + @override void setLooping(int playerId, bool looping) { log.add('setLooping');