diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 4c3adf08933a..5c4c8ac64845 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,5 +1,6 @@ ## NEXT +* Automatic Bitrate Streaming support to Android and iOS. * Updates minimum supported SDK version to Flutter 3.32/Dart 3.8. * Updates README to reflect currently supported OS versions for the latest versions of the endorsed platform implementations. diff --git a/packages/video_player/video_player/lib/src/adaptive_bitrate_manager.dart b/packages/video_player/video_player/lib/src/adaptive_bitrate_manager.dart new file mode 100644 index 000000000000..faf0f460220d --- /dev/null +++ b/packages/video_player/video_player/lib/src/adaptive_bitrate_manager.dart @@ -0,0 +1,94 @@ +import 'dart:async'; +import 'package:video_player_platform_interface/video_player_platform_interface.dart'; + +/// Automatic Adaptive Bitrate Manager for HLS Streaming +/// Manages quality automatically based on buffering and network conditions +class AdaptiveBitrateManager { + final int playerId; + final VideoPlayerPlatform _platform; + + late Timer _monitoringTimer; + int _bufferingCount = 0; + int _currentQualityLevel = 0; + DateTime _lastQualityChange = DateTime.now(); + bool _isMonitoring = false; + + // Quality presets (bits per second) + static const int quality360p = 500000; // 500 kbps + static const int quality480p = 800000; // 800 kbps + static const int quality720p = 1200000; // 1.2 Mbps + static const int quality1080p = 2500000; // 2.5 Mbps + + AdaptiveBitrateManager({ + required this.playerId, + required VideoPlayerPlatform platform, + }) : _platform = platform; + + /// Start automatic quality monitoring and adjustment + Future startAutoAdaptiveQuality() async { + if (_isMonitoring) return; + _isMonitoring = true; + + try { + await _platform.setBandwidthLimit(playerId, 0); + } catch (e) { + print('[AdaptiveBitrate] Error starting: $e'); + _isMonitoring = false; + return; + } + + _monitoringTimer = Timer.periodic(const Duration(seconds: 3), (_) async { + await _analyzeAndAdjust(); + }); + } + + /// Record a buffering event + void recordBufferingEvent() { + if (_isMonitoring) _bufferingCount++; + } + + /// Analyze network conditions and adjust quality + Future _analyzeAndAdjust() async { + // Don't adjust too frequently + if (DateTime.now().difference(_lastQualityChange).inSeconds < 5) { + return; + } + + int newQuality = _selectOptimalQuality(); + + if (newQuality != _currentQualityLevel) { + try { + await _platform.setBandwidthLimit(playerId, newQuality); + _currentQualityLevel = newQuality; + _lastQualityChange = DateTime.now(); + _bufferingCount = 0; + } catch (e) { + print('[AdaptiveBitrate] Error adjusting quality: $e'); + } + } + } + + /// Select optimal quality based on buffering and network conditions + int _selectOptimalQuality() { + // Conservative approach: start high, lower on buffering + if (_bufferingCount > 5) { + return quality360p; // Heavy buffering + } + + if (_bufferingCount > 2) { + return quality480p; // Moderate buffering + } + + if (_bufferingCount == 0) { + return quality1080p; // No buffering - try high quality + } + + return quality720p; // Default middle quality + } + + /// Stop automatic quality management + void dispose() { + _isMonitoring = false; + _monitoringTimer.cancel(); + } +} diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index f0589cb46869..5c6c41350bcf 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -12,6 +12,7 @@ import 'package:flutter/services.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'src/closed_caption_file.dart'; +import 'src/adaptive_bitrate_manager.dart'; export 'package:video_player_platform_interface/video_player_platform_interface.dart' show @@ -24,6 +25,7 @@ export 'package:video_player_platform_interface/video_player_platform_interface. VideoViewType; export 'src/closed_caption_file.dart'; +export 'src/adaptive_bitrate_manager.dart'; VideoPlayerPlatform? _lastVideoPlayerPlatform; @@ -409,6 +411,7 @@ class VideoPlayerController extends ValueNotifier { Completer? _creatingCompleter; StreamSubscription? _eventSubscription; _VideoAppLifeCycleObserver? _lifeCycleObserver; + AdaptiveBitrateManager? _adaptiveBitrateManager; /// The id of a player that hasn't been initialized. @visibleForTesting @@ -473,6 +476,14 @@ class VideoPlayerController extends ValueNotifier { (await _videoPlayerPlatform.createWithOptions(creationOptions)) ?? kUninitializedPlayerId; _creatingCompleter!.complete(null); + + // Initialize automatic adaptive bitrate management for HLS + _adaptiveBitrateManager = AdaptiveBitrateManager( + playerId: _playerId, + platform: _videoPlayerPlatform, + ); + await _adaptiveBitrateManager!.startAutoAdaptiveQuality(); + final initializingCompleter = Completer(); // Apply the web-specific options @@ -523,6 +534,7 @@ class VideoPlayerController extends ValueNotifier { value = value.copyWith(buffered: event.buffered); case VideoEventType.bufferingStart: value = value.copyWith(isBuffering: true); + _adaptiveBitrateManager?.recordBufferingEvent(); case VideoEventType.bufferingEnd: value = value.copyWith(isBuffering: false); case VideoEventType.isPlayingStateUpdate: @@ -569,6 +581,7 @@ class VideoPlayerController extends ValueNotifier { if (!_isDisposed) { _isDisposed = true; _timer?.cancel(); + _adaptiveBitrateManager?.dispose(); await _eventSubscription?.cancel(); await _videoPlayerPlatform.dispose(_playerId); } @@ -735,6 +748,44 @@ class VideoPlayerController extends ValueNotifier { await _applyPlaybackSpeed(); } + /// Sets the bandwidth limit for HLS adaptive bitrate streaming. + /// + /// This method limits the maximum bandwidth used for video playback, + /// which affects which HLS variant streams the player can select. + /// + /// The native player will only select video variants with bitrate + /// less than or equal to the specified [maxBandwidthBps]. + /// + /// Platforms: + /// - **Android**: Uses ExoPlayer's DefaultTrackSelector.setMaxVideoBitrate() + /// - **iOS/macOS**: Uses AVPlayer's preferredPeakBitRate property + /// - **Web**: Not supported (no-op) + /// + /// Parameters: + /// - [maxBandwidthBps]: Maximum bandwidth in bits per second. + /// * 0 or negative: No limit (player auto-selects) + /// * Positive value: Player selects variants ≤ this bandwidth + /// + /// Example: + /// ```dart + /// // Limit to 720p quality (~1.2 Mbps) + /// await controller.setBandwidthLimit(1200000); + /// + /// // No limit - let player decide + /// await controller.setBandwidthLimit(0); + /// ``` + /// + /// Note: This is useful for HLS streams where you want to control + /// quality selection without reinitializing the player. The player + /// will seamlessly switch to appropriate variants as bandwidth + /// changes within the limit. + Future setBandwidthLimit(int maxBandwidthBps) async { + if (_isDisposedOrNotInitialized) { + return; + } + await _videoPlayerPlatform.setBandwidthLimit(_playerId, maxBandwidthBps); + } + /// Sets the caption offset. /// /// The [offset] will be used when getting the correct caption for a specific position. diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index a71e45323394..8263df669488 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -25,9 +25,9 @@ dependencies: flutter: sdk: flutter html: ^0.15.0 - video_player_android: ^2.8.1 - video_player_avfoundation: ^2.7.0 - video_player_platform_interface: ^6.3.0 + video_player_android: ^2.9.3 + video_player_avfoundation: ^2.9.1 + video_player_platform_interface: ^6.6.0 video_player_web: ^2.1.0 dev_dependencies: diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index 560455b21001..c5783877f8a4 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Automatic Bitrate Streaming support to Android. + ## 2.9.2 * Bumps kotlin_version to 2.3.0. diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerEventListener.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerEventListener.java index 33988786a78a..d70fbf8117c0 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerEventListener.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerEventListener.java @@ -7,9 +7,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.Format; import androidx.media3.common.PlaybackException; import androidx.media3.common.Player; import androidx.media3.common.Tracks; +import androidx.media3.common.VideoSize; import androidx.media3.exoplayer.ExoPlayer; public abstract class ExoPlayerEventListener implements Player.Listener { @@ -17,6 +19,11 @@ public abstract class ExoPlayerEventListener implements Player.Listener { protected final ExoPlayer exoPlayer; protected final VideoPlayerCallbacks events; + // Track current video quality for adaptive streaming logging + private int currentVideoWidth = 0; + private int currentVideoHeight = 0; + private int currentVideoBitrate = 0; + protected enum RotationDegrees { ROTATE_0(0), ROTATE_90(90), @@ -54,22 +61,29 @@ public ExoPlayerEventListener( @Override public void onPlaybackStateChanged(final int playbackState) { PlatformPlaybackState platformState = PlatformPlaybackState.UNKNOWN; + switch (playbackState) { case Player.STATE_BUFFERING: platformState = PlatformPlaybackState.BUFFERING; + android.util.Log.d("ExoPlayerListener", "State: BUFFERING"); break; case Player.STATE_READY: platformState = PlatformPlaybackState.READY; if (!isInitialized) { isInitialized = true; sendInitialized(); + android.util.Log.d("ExoPlayerListener", "State: READY - Video initialized"); + } else { + android.util.Log.d("ExoPlayerListener", "State: READY"); } break; case Player.STATE_ENDED: platformState = PlatformPlaybackState.ENDED; + android.util.Log.d("ExoPlayerListener", "State: ENDED"); break; case Player.STATE_IDLE: platformState = PlatformPlaybackState.IDLE; + android.util.Log.d("ExoPlayerListener", "State: IDLE"); break; } events.onPlaybackStateChanged(platformState); @@ -77,28 +91,102 @@ public void onPlaybackStateChanged(final int playbackState) { @Override public void onPlayerError(@NonNull final PlaybackException error) { + String errorMessage = error.getMessage(); + if (errorMessage == null) { + errorMessage = "Unknown error"; + } + android.util.Log.e("ExoPlayerListener", "Player error: " + errorMessage, error); + if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) { // See // https://exoplayer.dev/live-streaming.html#behindlivewindowexception-and-error_code_behind_live_window + android.util.Log.d("ExoPlayerListener", "Behind live window - seeking to default position"); exoPlayer.seekToDefaultPosition(); exoPlayer.prepare(); } else { - events.onError("VideoError", "Video player had error " + error, null); + events.onError("VideoError", "Video player had error " + errorMessage, null); } } @Override public void onIsPlayingChanged(boolean isPlaying) { + android.util.Log.d("ExoPlayerListener", "Is playing changed: " + isPlaying); events.onIsPlayingStateUpdate(isPlaying); } @Override public void onTracksChanged(@NonNull Tracks tracks) { + // Log adaptive bitrate streaming quality changes + logCurrentVideoQuality(tracks); + // Find the currently selected audio track and notify String selectedTrackId = findSelectedAudioTrackId(tracks); + android.util.Log.d("ExoPlayerListener", "Tracks changed - Selected audio track: " + selectedTrackId); events.onAudioTrackChanged(selectedTrackId); } + @Override + public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + // This is called when adaptive bitrate streaming changes the video resolution + if (currentVideoWidth != videoSize.width || currentVideoHeight != videoSize.height) { + currentVideoWidth = videoSize.width; + currentVideoHeight = videoSize.height; + + String quality = getQualityLabel(videoSize.height); + android.util.Log.i("ExoPlayerListener", + "📹 ADAPTIVE QUALITY CHANGE: " + quality + " (" + videoSize.width + "x" + videoSize.height + ")"); + } + } + + /** + * Logs the current video quality being played (for adaptive streaming monitoring) + */ + private void logCurrentVideoQuality(@NonNull Tracks tracks) { + // Find selected video track + for (Tracks.Group group : tracks.getGroups()) { + if (group.getType() == C.TRACK_TYPE_VIDEO && group.isSelected()) { + for (int i = 0; i < group.length; i++) { + if (group.isTrackSelected(i)) { + Format format = group.getTrackFormat(i); + + // Only log if quality changed + if (currentVideoBitrate != format.bitrate || + currentVideoWidth != format.width || + currentVideoHeight != format.height) { + + currentVideoBitrate = format.bitrate; + currentVideoWidth = format.width; + currentVideoHeight = format.height; + + String quality = getQualityLabel(format.height); + String bitrate = format.bitrate != Format.NO_VALUE ? + String.format("%.2f Mbps", format.bitrate / 1_000_000.0) : "unknown"; + + android.util.Log.i("ExoPlayerListener", + "🎬 ADAPTIVE STREAMING: Now playing " + quality + " at " + bitrate + + " [" + format.width + "x" + format.height + "]"); + } + return; + } + } + } + } + } + + /** + * Gets a human-readable quality label based on video height + */ + private String getQualityLabel(int height) { + if (height >= 2160) return "4K"; + if (height >= 1440) return "1440p"; + if (height >= 1080) return "1080p"; + if (height >= 720) return "720p"; + if (height >= 480) return "480p"; + if (height >= 360) return "360p"; + if (height >= 240) return "240p"; + return height + "p"; + } + /** * Finds the ID of the currently selected audio track. * @@ -121,4 +209,4 @@ private String findSelectedAudioTrackId(@NonNull Tracks tracks) { } return null; } -} +} \ No newline at end of file diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/HttpVideoAsset.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/HttpVideoAsset.java index 0c85e0e8267b..116a7075df58 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/HttpVideoAsset.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/HttpVideoAsset.java @@ -63,21 +63,23 @@ public MediaSource.Factory getMediaSourceFactory(@NonNull Context context) { return getMediaSourceFactory(context, new DefaultHttpDataSource.Factory()); } - /** - * Returns a configured media source factory, starting at the provided factory. - * - *

This method is provided for ease of testing without making real HTTP calls. - * - * @param context application context. - * @param initialFactory initial factory, to be configured. - * @return configured factory, or {@code null} if not needed for this asset type. - */ @VisibleForTesting MediaSource.Factory getMediaSourceFactory( Context context, DefaultHttpDataSource.Factory initialFactory) { - unstableUpdateDataSourceFactory(initialFactory, httpHeaders, userAgent); + unstableUpdateDataSourceFactory(initialFactory, httpHeaders, userAgent, assetUrl, streamingFormat); DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(context, initialFactory); - return new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory); + + // Configure DefaultMediaSourceFactory - ExoPlayer handles ABR automatically + DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(context) + .setDataSourceFactory(dataSourceFactory); + + // Log adaptive streaming readiness + if (streamingFormat == StreamingFormat.HTTP_LIVE || streamingFormat == StreamingFormat.DYNAMIC_ADAPTIVE) { + android.util.Log.i("HttpVideoAsset", + "✅ " + streamingFormat.name() + " stream configured - adaptive bitrate streaming READY"); + } + + return mediaSourceFactory; } // TODO: Migrate to stable API, see https://github.com/flutter/flutter/issues/147039. @@ -85,10 +87,40 @@ MediaSource.Factory getMediaSourceFactory( private static void unstableUpdateDataSourceFactory( @NonNull DefaultHttpDataSource.Factory factory, @NonNull Map httpHeaders, - @Nullable String userAgent) { + @Nullable String userAgent, + @Nullable String assetUrl, + @NonNull StreamingFormat streamingFormat) { + factory.setUserAgent(userAgent).setAllowCrossProtocolRedirects(true); + if (!httpHeaders.isEmpty()) { factory.setDefaultRequestProperties(httpHeaders); } + + // Enhanced logging for adaptive streaming + android.util.Log.d("HttpVideoAsset", "========== VIDEO INITIALIZATION =========="); + android.util.Log.d("HttpVideoAsset", "Video URL: " + assetUrl); + android.util.Log.d("HttpVideoAsset", "Streaming Format: " + streamingFormat.name()); + android.util.Log.d("HttpVideoAsset", "User-Agent: " + userAgent); + android.util.Log.d("HttpVideoAsset", "HTTP Headers count: " + httpHeaders.size()); + android.util.Log.d("HttpVideoAsset", "Allow Cross-Protocol Redirects: true"); + + // Log streaming format specifics + switch (streamingFormat) { + case HTTP_LIVE: + android.util.Log.i("HttpVideoAsset", "🎯 HLS ADAPTIVE STREAMING - ExoPlayer will automatically switch between quality variants (360p/480p/720p/1080p) based on network speed"); + break; + case DYNAMIC_ADAPTIVE: + android.util.Log.i("HttpVideoAsset", "🎯 DASH ADAPTIVE STREAMING - ExoPlayer will automatically switch between quality variants based on network speed"); + break; + case SMOOTH: + android.util.Log.i("HttpVideoAsset", "🎯 SMOOTH STREAMING - ExoPlayer will handle adaptive quality"); + break; + case UNKNOWN: + android.util.Log.d("HttpVideoAsset", "Format unknown - using default configuration (progressive download)"); + break; + } + + android.util.Log.d("HttpVideoAsset", "========== END INITIALIZATION =========="); } -} +} \ No newline at end of file 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 7cfb5c1c13be..fc0ee4c21253 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 @@ -233,10 +233,53 @@ public void selectAudioTrack(long groupIndex, long trackIndex) { trackSelector.buildUponParameters().setOverrideForType(override).build()); } + // TODO: Migrate to stable API, see https://github.com/flutter/flutter/issues/147039. + @UnstableApi + @Override + public void setBandwidthLimit(long maxBandwidthBps) { + if (trackSelector == null) { + android.util.Log.w("VideoPlayer", "Cannot set bandwidth limit: track selector is null"); + return; + } + + // Build the new track selector parameters with bandwidth limit + DefaultTrackSelector.Parameters.Builder parametersBuilder = + trackSelector.buildUponParameters(); + + if (maxBandwidthBps <= 0) { + // Remove bandwidth limit - allow full adaptive streaming + parametersBuilder + .setMaxVideoBitrate(Integer.MAX_VALUE); + + android.util.Log.d("VideoPlayer", "Bandwidth limit removed - full adaptive streaming enabled"); + } else { + // Set the maximum video bitrate to limit bandwidth usage + // This still allows adaptive streaming BELOW this limit + int maxBitrate = maxBandwidthBps > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) maxBandwidthBps; + parametersBuilder.setMaxVideoBitrate(maxBitrate); + + android.util.Log.d("VideoPlayer", "Bandwidth limit set to: " + maxBitrate + " bps - adaptive streaming below this limit"); + } + + // Apply the new parameters + trackSelector.setParameters(parametersBuilder.build()); + } + + /** + * @deprecated This method DISABLES adaptive bitrate streaming. Do not use. + * ExoPlayer's adaptive streaming is now enabled by default in the player creation. + */ + @Deprecated + @UnstableApi + public void enableSmoothAdaptiveStreaming() { + android.util.Log.w("VideoPlayer", "enableSmoothAdaptiveStreaming() is deprecated and should not be called. It disables ABR."); + // This method is now a no-op to prevent disabling ABR + } + public void dispose() { if (disposeHandler != null) { disposeHandler.onDispose(); } exoPlayer.release(); } -} +} \ No newline at end of file diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformVideoView.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformVideoView.java index 35fd66283231..30fd4e3383a4 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformVideoView.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformVideoView.java @@ -5,90 +5,94 @@ package io.flutter.plugins.videoplayer.platformview; import android.content.Context; -import android.os.Build; -import android.view.SurfaceHolder; -import android.view.SurfaceView; +import android.graphics.SurfaceTexture; +import android.view.Surface; +import android.view.TextureView; import android.view.View; import androidx.annotation.NonNull; -import androidx.annotation.OptIn; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; import io.flutter.plugin.platform.PlatformView; -/** - * A class used to create a native video view that can be embedded in a Flutter app. It wraps an - * {@link ExoPlayer} instance and displays its video content. - */ +@UnstableApi public final class PlatformVideoView implements PlatformView { - @NonNull private final SurfaceView surfaceView; - /** - * Constructs a new PlatformVideoView. - * - * @param context The context in which the view is running. - * @param exoPlayer The ExoPlayer instance used to play the video. - */ - @OptIn(markerClass = UnstableApi.class) - public PlatformVideoView(@NonNull Context context, @NonNull ExoPlayer exoPlayer) { - surfaceView = new SurfaceView(context); + @NonNull + private final TextureView textureView; - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { - // Workaround for rendering issues on Android 9 (API 28). - // On Android 9, using setVideoSurfaceView seems to lead to issues where the first frame is - // not displayed if the video is paused initially. - // To ensure the first frame is visible, the surface is directly set using holder.getSurface() - // when the surface is created, and ExoPlayer seeks to a position to force rendering of the - // first frame. - setupSurfaceWithCallback(exoPlayer); - } else { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { - // Avoid blank space instead of a video on Android versions below 8 by adjusting video's - // z-layer within the Android view hierarchy: - surfaceView.setZOrderMediaOverlay(true); - } - exoPlayer.setVideoSurfaceView(surfaceView); - } - } + @NonNull + private final ExoPlayer exoPlayer; + + private Surface surface; - private void setupSurfaceWithCallback(@NonNull ExoPlayer exoPlayer) { - surfaceView - .getHolder() - .addCallback( - new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(@NonNull SurfaceHolder holder) { - exoPlayer.setVideoSurface(holder.getSurface()); - // Force first frame rendering: - exoPlayer.seekTo(1); - } + public PlatformVideoView( + @NonNull Context context, + @NonNull ExoPlayer exoPlayer + ) { + this.exoPlayer = exoPlayer; + this.textureView = new TextureView(context); - @Override - public void surfaceChanged( - @NonNull SurfaceHolder holder, int format, int width, int height) { - // No implementation needed. - } + textureView.setSurfaceTextureListener( + new TextureView.SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable( + @NonNull SurfaceTexture surfaceTexture, + int width, + int height + ) { + surface = new Surface(surfaceTexture); + exoPlayer.setVideoSurface(surface); + } - @Override - public void surfaceDestroyed(@NonNull SurfaceHolder holder) { - exoPlayer.setVideoSurface(null); - } - }); - } + @Override + public void onSurfaceTextureSizeChanged( + @NonNull SurfaceTexture surfaceTexture, + int width, + int height + ) { + // ExoPlayer handles HLS adaptive bitrate resolution changes automatically. + // No need to recreate the surface - doing so causes frame misalignment issues. + // The MediaCodec decoder will seamlessly adapt to the new resolution. + } - /** - * Returns the view associated with this PlatformView. - * - * @return The SurfaceView used to display the video. - */ - @NonNull - @Override - public View getView() { - return surfaceView; - } + @Override + public boolean onSurfaceTextureDestroyed( + @NonNull SurfaceTexture surfaceTexture + ) { + exoPlayer.setVideoSurface(null); + if (surface != null) { + surface.release(); + surface = null; + } + return true; // TextureView can safely release the SurfaceTexture + } + + @Override + public void onSurfaceTextureUpdated( + @NonNull SurfaceTexture surfaceTexture + ) { + // No-op + } + } + ); + } - /** Disposes of the resources used by this PlatformView. */ - @Override - public void dispose() { - surfaceView.getHolder().getSurface().release(); - } -} + @NonNull + @Override + public View getView() { + return textureView; + } + + @Override + public void dispose() { + // Remove listener to prevent callback leaks + textureView.setSurfaceTextureListener(null); + + exoPlayer.setVideoSurface(null); + + if (surface != null) { + surface.release(); + surface = null; + } + } +} \ No newline at end of file diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewExoPlayerEventListener.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewExoPlayerEventListener.java index 699eab6eb0c1..89de929cde77 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewExoPlayerEventListener.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewExoPlayerEventListener.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import androidx.annotation.OptIn; import androidx.media3.common.Format; +import androidx.media3.common.VideoSize; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; import io.flutter.plugins.videoplayer.ExoPlayerEventListener; @@ -14,6 +15,11 @@ import java.util.Objects; public final class PlatformViewExoPlayerEventListener extends ExoPlayerEventListener { + + // Track the initial format to avoid re-initialization on resolution changes + private Format initialVideoFormat = null; + private boolean hasInitialized = false; + public PlatformViewExoPlayerEventListener( @NonNull ExoPlayer exoPlayer, @NonNull VideoPlayerCallbacks events) { super(exoPlayer, events); @@ -22,11 +28,24 @@ public PlatformViewExoPlayerEventListener( @OptIn(markerClass = UnstableApi.class) @Override protected void sendInitialized() { - // We can't rely on VideoSize here, because at this point it is not available - the platform - // view was not created yet. We use the video format instead. + // Only send initialization once with the initial format + // Subsequent resolution changes are handled by ExoPlayer automatically + if (hasInitialized) { + return; + } + Format videoFormat = exoPlayer.getVideoFormat(); + if (videoFormat == null) { + // Format not available yet, wait for next callback + return; + } + + // Store the initial format + initialVideoFormat = videoFormat; + hasInitialized = true; + RotationDegrees rotationCorrection = - RotationDegrees.fromDegrees(Objects.requireNonNull(videoFormat).rotationDegrees); + RotationDegrees.fromDegrees(videoFormat.rotationDegrees); int width = videoFormat.width; int height = videoFormat.height; @@ -36,10 +55,23 @@ protected void sendInitialized() { || rotationCorrection == RotationDegrees.ROTATE_270) { width = videoFormat.height; height = videoFormat.width; - rotationCorrection = RotationDegrees.fromDegrees(0); } + android.util.Log.d("PlatformViewListener", + "Initialized with resolution: " + width + "x" + height); + events.onInitialized(width, height, exoPlayer.getDuration(), rotationCorrection.getDegrees()); } -} + + @Override + public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + // Log resolution changes for debugging + android.util.Log.d("PlatformViewListener", + "Resolution changed to: " + videoSize.width + "x" + videoSize.height); + + // Don't re-initialize - let ExoPlayer and TextureView handle the resize + // The TextureView will automatically adapt to new dimensions + // This prevents the frame split issue during 720p -> 480p -> 360p transitions + } +} \ No newline at end of file diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java index a7c079773b58..f2ac123babd9 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java @@ -11,6 +11,9 @@ import androidx.media3.common.MediaItem; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection; +import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; +import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; import io.flutter.plugins.videoplayer.ExoPlayerEventListener; import io.flutter.plugins.videoplayer.VideoAsset; import io.flutter.plugins.videoplayer.VideoPlayer; @@ -51,19 +54,57 @@ public static PlatformViewVideoPlayer create( @NonNull VideoPlayerCallbacks events, @NonNull VideoAsset asset, @NonNull VideoPlayerOptions options) { - return new PlatformViewVideoPlayer( + + PlatformViewVideoPlayer player = new PlatformViewVideoPlayer( events, asset.getMediaItem(), options, () -> { - androidx.media3.exoplayer.trackselection.DefaultTrackSelector trackSelector = - new androidx.media3.exoplayer.trackselection.DefaultTrackSelector(context); + // Create bandwidth meter for adaptive streaming + DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(context) + .setInitialBitrateEstimate(1_000_000) // Start with 1 Mbps estimate + .build(); + + // Create adaptive track selection factory + AdaptiveTrackSelection.Factory trackSelectionFactory = + new AdaptiveTrackSelection.Factory(); + + DefaultTrackSelector trackSelector = new DefaultTrackSelector(context, trackSelectionFactory); + + // Configure for YouTube-style adaptive streaming + trackSelector.setParameters( + trackSelector + .buildUponParameters() + // ENABLE adaptive bitrate streaming + .setAllowVideoNonSeamlessAdaptiveness(true) // Allow quality switches even with brief buffering + .setAllowVideoMixedMimeTypeAdaptiveness(false) // Keep same codec for stability + .setAllowVideoMixedDecoderSupportAdaptiveness(true) // Allow decoder adaptiveness + // Audio settings + .setAllowAudioMixedMimeTypeAdaptiveness(false) + .setAllowAudioMixedSampleRateAdaptiveness(true) + .setAllowAudioMixedChannelCountAdaptiveness(true) + // Don't force lowest bitrate - let ExoPlayer choose based on network + .setForceLowestBitrate(false) + .setForceHighestSupportedBitrate(false) + // Let ExoPlayer adapt based on network conditions + .setMaxVideoBitrate(Integer.MAX_VALUE) + .build()); + + android.util.Log.d("PlatformViewVideoPlayer", "Adaptive bitrate streaming ENABLED - ExoPlayer will automatically switch qualities based on network speed"); + ExoPlayer.Builder builder = new ExoPlayer.Builder(context) .setTrackSelector(trackSelector) + .setBandwidthMeter(bandwidthMeter) // Attach bandwidth meter .setMediaSourceFactory(asset.getMediaSourceFactory(context)); + return builder.build(); }); + + // DO NOT call enableSmoothAdaptiveStreaming() - it disables ABR! + // ExoPlayer is now configured for automatic adaptive streaming + + return player; } @NonNull @@ -72,4 +113,4 @@ protected ExoPlayerEventListener createExoPlayerEventListener( @NonNull ExoPlayer exoPlayer, @Nullable SurfaceProducer surfaceProducer) { return new PlatformViewExoPlayerEventListener(exoPlayer, videoPlayerEvents); } -} +} \ No newline at end of file diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureExoPlayerEventListener.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureExoPlayerEventListener.java index bcc901b7218f..75f1be8feaa7 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureExoPlayerEventListener.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureExoPlayerEventListener.java @@ -8,6 +8,7 @@ import androidx.annotation.OptIn; import androidx.media3.common.Format; import androidx.media3.common.VideoSize; +import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; import io.flutter.plugins.videoplayer.ExoPlayerEventListener; import io.flutter.plugins.videoplayer.VideoPlayerCallbacks; @@ -15,6 +16,7 @@ public final class TextureExoPlayerEventListener extends ExoPlayerEventListener { private final boolean surfaceProducerHandlesCropAndRotation; + private boolean hasInitialized = false; public TextureExoPlayerEventListener( @NonNull ExoPlayer exoPlayer, @@ -24,34 +26,62 @@ public TextureExoPlayerEventListener( this.surfaceProducerHandlesCropAndRotation = surfaceProducerHandlesCropAndRotation; } + @OptIn(markerClass = UnstableApi.class) @Override protected void sendInitialized() { + // Only send initialization once with the initial format + // Subsequent resolution changes are handled by ExoPlayer automatically + if (hasInitialized) { + return; + } + VideoSize videoSize = exoPlayer.getVideoSize(); RotationDegrees rotationCorrection = RotationDegrees.ROTATE_0; int width = videoSize.width; int height = videoSize.height; - if (width != 0 && height != 0) { - // When the SurfaceTexture backend for Impeller is used, the preview should already - // be correctly rotated. - if (!surfaceProducerHandlesCropAndRotation) { - // The video's Format also provides a rotation correction that may be used to - // correct the rotation, so we try to use that to correct the video rotation - // when the ImageReader backend for Impeller is used. - int rawVideoFormatRotation = getRotationCorrectionFromFormat(exoPlayer); - - try { - rotationCorrection = RotationDegrees.fromDegrees(rawVideoFormatRotation); - } catch (IllegalArgumentException e) { - // Rotation correction other than 0, 90, 180, 270 reported by Format. Because this is - // unexpected we apply no rotation correction. - rotationCorrection = RotationDegrees.ROTATE_0; - } + + if (width == 0 || height == 0) { + // Video size not available yet, wait for next callback + return; + } + + hasInitialized = true; + + // When the SurfaceTexture backend for Impeller is used, the preview should already + // be correctly rotated. + if (!surfaceProducerHandlesCropAndRotation) { + // The video's Format also provides a rotation correction that may be used to + // correct the rotation, so we try to use that to correct the video rotation + // when the ImageReader backend for Impeller is used. + int rawVideoFormatRotation = getRotationCorrectionFromFormat(exoPlayer); + + try { + rotationCorrection = RotationDegrees.fromDegrees(rawVideoFormatRotation); + } catch (IllegalArgumentException e) { + // Rotation correction other than 0, 90, 180, 270 reported by Format. Because this is + // unexpected we apply no rotation correction. + rotationCorrection = RotationDegrees.ROTATE_0; } } + + android.util.Log.d("TextureListener", + "Initialized with resolution: " + width + "x" + height + + ", rotation: " + rotationCorrection.getDegrees()); + events.onInitialized(width, height, exoPlayer.getDuration(), rotationCorrection.getDegrees()); } - @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) + @Override + public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + android.util.Log.d("TextureListener", + "Resolution changed to: " + videoSize.width + "x" + videoSize.height); + + // Don't re-initialize - let ExoPlayer handle the resize + // The texture surface will automatically adapt to new dimensions + // This prevents the frame split issue during 720p -> 480p -> 360p transitions + } + + @OptIn(markerClass = UnstableApi.class) // A video's Format and its rotation degrees are unstable because they are not guaranteed // the same implementation across API versions. It is possible that this logic may need // revisiting should the implementation change across versions of the Exoplayer API. @@ -59,4 +89,4 @@ private int getRotationCorrectionFromFormat(ExoPlayer exoPlayer) { Format videoFormat = Objects.requireNonNull(exoPlayer.getVideoFormat()); return videoFormat.rotationDegrees; } -} +} \ No newline at end of file diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java index e482bdd85020..02cb4d36a67e 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java @@ -13,6 +13,9 @@ import androidx.media3.common.MediaItem; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection; +import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; +import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; import io.flutter.plugins.videoplayer.ExoPlayerEventListener; import io.flutter.plugins.videoplayer.VideoAsset; import io.flutter.plugins.videoplayer.VideoPlayer; @@ -30,6 +33,7 @@ public final class TextureVideoPlayer extends VideoPlayer implements SurfaceProducer.Callback { // True when the ExoPlayer instance has a null surface. private boolean needsSurface = true; + /** * Creates a texture video player. * @@ -49,20 +53,58 @@ public static TextureVideoPlayer create( @NonNull SurfaceProducer surfaceProducer, @NonNull VideoAsset asset, @NonNull VideoPlayerOptions options) { - return new TextureVideoPlayer( + + TextureVideoPlayer player = new TextureVideoPlayer( events, surfaceProducer, asset.getMediaItem(), options, () -> { - androidx.media3.exoplayer.trackselection.DefaultTrackSelector trackSelector = - new androidx.media3.exoplayer.trackselection.DefaultTrackSelector(context); + // Create bandwidth meter for adaptive streaming + DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(context) + .setInitialBitrateEstimate(1_000_000) // Start with 1 Mbps estimate + .build(); + + // Create adaptive track selection factory + AdaptiveTrackSelection.Factory trackSelectionFactory = + new AdaptiveTrackSelection.Factory(); + + DefaultTrackSelector trackSelector = new DefaultTrackSelector(context, trackSelectionFactory); + + // Configure for YouTube-style adaptive streaming + trackSelector.setParameters( + trackSelector + .buildUponParameters() + // ENABLE adaptive bitrate streaming + .setAllowVideoNonSeamlessAdaptiveness(true) // Allow quality switches even with brief buffering + .setAllowVideoMixedMimeTypeAdaptiveness(false) // Keep same codec for stability + .setAllowVideoMixedDecoderSupportAdaptiveness(true) // Allow decoder adaptiveness + // Audio settings + .setAllowAudioMixedMimeTypeAdaptiveness(false) + .setAllowAudioMixedSampleRateAdaptiveness(true) + .setAllowAudioMixedChannelCountAdaptiveness(true) + // Don't force lowest bitrate - let ExoPlayer choose based on network + .setForceLowestBitrate(false) + .setForceHighestSupportedBitrate(false) + // Let ExoPlayer adapt based on network conditions + .setMaxVideoBitrate(Integer.MAX_VALUE) + .build()); + + android.util.Log.d("TextureVideoPlayer", "Adaptive bitrate streaming ENABLED - ExoPlayer will automatically switch qualities based on network speed"); + ExoPlayer.Builder builder = new ExoPlayer.Builder(context) .setTrackSelector(trackSelector) + .setBandwidthMeter(bandwidthMeter) // Attach bandwidth meter .setMediaSourceFactory(asset.getMediaSourceFactory(context)); + return builder.build(); }); + + // DO NOT call enableSmoothAdaptiveStreaming() - it disables ABR! + // ExoPlayer is now configured for automatic adaptive streaming + + return player; } // TODO: Migrate to stable API, see https://github.com/flutter/flutter/issues/147039. @@ -120,4 +162,4 @@ public void dispose() { assert surfaceProducer != null; surfaceProducer.release(); } -} +} \ No newline at end of file diff --git a/packages/video_player/video_player_android/android/src/main/kotlin/io/flutter/plugins/videoplayer/Messages.kt b/packages/video_player/video_player_android/android/src/main/kotlin/io/flutter/plugins/videoplayer/Messages.kt index e546c744e561..0f393df6351d 100644 --- a/packages/video_player/video_player_android/android/src/main/kotlin/io/flutter/plugins/videoplayer/Messages.kt +++ b/packages/video_player/video_player_android/android/src/main/kotlin/io/flutter/plugins/videoplayer/Messages.kt @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.5), do not edit directly. +// Autogenerated from Pigeon (v26.1.7), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -12,11 +12,10 @@ import io.flutter.plugin.common.BasicMessageChannel import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MessageCodec -import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec import java.io.ByteArrayOutputStream import java.nio.ByteBuffer - private object MessagesPigeonUtils { fun wrapResult(result: Any?): List { @@ -25,53 +24,61 @@ private object MessagesPigeonUtils { fun wrapError(exception: Throwable): List { return if (exception is FlutterError) { - listOf(exception.code, exception.message, exception.details) + listOf( + exception.code, + exception.message, + exception.details + ) } else { listOf( - exception.javaClass.simpleName, - exception.toString(), - "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) } } - fun deepEquals(a: Any?, b: Any?): Boolean { if (a is ByteArray && b is ByteArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is IntArray && b is IntArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is LongArray && b is LongArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is DoubleArray && b is DoubleArray) { - return a.contentEquals(b) + return a.contentEquals(b) } if (a is Array<*> && b is Array<*>) { - return a.size == b.size && a.indices.all { deepEquals(a[it], b[it]) } + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } } if (a is List<*> && b is List<*>) { - return a.size == b.size && a.indices.all { deepEquals(a[it], b[it]) } + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } } if (a is Map<*, *> && b is Map<*, *>) { - return a.size == b.size && - a.all { (b as Map).contains(it.key) && deepEquals(it.value, b[it.key]) } + return a.size == b.size && a.all { + (b as Map).contains(it.key) && + deepEquals(it.value, b[it.key]) + } } return a == b } + } /** * Error class for passing custom error details to Flutter via a thrown PlatformException. - * * @property code The error code. * @property message The error message. * @property details The error details. Must be a datatype supported by the api codec. */ -class FlutterError( - val code: String, - override val message: String? = null, - val details: Any? = null +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null ) : Throwable() /** Pigeon equivalent of video_platform_interface's VideoFormat. */ @@ -106,25 +113,26 @@ enum class PlatformPlaybackState(val raw: Int) { } /** - * Generated class from Pigeon that represents data sent in messages. This class should not be - * extended by any user class outside of the generated file. + * Generated class from Pigeon that represents data sent in messages. + * This class should not be extended by any user class outside of the generated file. */ -sealed class PlatformVideoEvent +sealed class PlatformVideoEvent /** * Sent when the video is initialized and ready to play. * * Generated class from Pigeon that represents data sent in messages. */ -data class InitializationEvent( - /** The video duration in milliseconds. */ - val duration: Long, - /** The width of the video in pixels. */ - val width: Long, - /** The height of the video in pixels. */ - val height: Long, - /** The rotation that should be applied during playback. */ - val rotationCorrection: Long -) : PlatformVideoEvent() { +data class InitializationEvent ( + /** The video duration in milliseconds. */ + val duration: Long, + /** The width of the video in pixels. */ + val width: Long, + /** The height of the video in pixels. */ + val height: Long, + /** The rotation that should be applied during playback. */ + val rotationCorrection: Long +) : PlatformVideoEvent() + { companion object { fun fromList(pigeonVar_list: List): InitializationEvent { val duration = pigeonVar_list[0] as Long @@ -134,16 +142,14 @@ data class InitializationEvent( return InitializationEvent(duration, width, height, rotationCorrection) } } - fun toList(): List { return listOf( - duration, - width, - height, - rotationCorrection, + duration, + width, + height, + rotationCorrection, ) } - override fun equals(other: Any?): Boolean { if (other !is InitializationEvent) { return false @@ -151,8 +157,7 @@ data class InitializationEvent( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -164,20 +169,21 @@ data class InitializationEvent( * * Generated class from Pigeon that represents data sent in messages. */ -data class PlaybackStateChangeEvent(val state: PlatformPlaybackState) : PlatformVideoEvent() { +data class PlaybackStateChangeEvent ( + val state: PlatformPlaybackState +) : PlatformVideoEvent() + { companion object { fun fromList(pigeonVar_list: List): PlaybackStateChangeEvent { val state = pigeonVar_list[0] as PlatformPlaybackState return PlaybackStateChangeEvent(state) } } - fun toList(): List { return listOf( - state, + state, ) } - override fun equals(other: Any?): Boolean { if (other !is PlaybackStateChangeEvent) { return false @@ -185,8 +191,7 @@ data class PlaybackStateChangeEvent(val state: PlatformPlaybackState) : Platform if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -198,20 +203,21 @@ data class PlaybackStateChangeEvent(val state: PlatformPlaybackState) : Platform * * Generated class from Pigeon that represents data sent in messages. */ -data class IsPlayingStateEvent(val isPlaying: Boolean) : PlatformVideoEvent() { +data class IsPlayingStateEvent ( + val isPlaying: Boolean +) : PlatformVideoEvent() + { companion object { fun fromList(pigeonVar_list: List): IsPlayingStateEvent { val isPlaying = pigeonVar_list[0] as Boolean return IsPlayingStateEvent(isPlaying) } } - fun toList(): List { return listOf( - isPlaying, + isPlaying, ) } - override fun equals(other: Any?): Boolean { if (other !is IsPlayingStateEvent) { return false @@ -219,8 +225,7 @@ data class IsPlayingStateEvent(val isPlaying: Boolean) : PlatformVideoEvent() { if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -228,28 +233,27 @@ data class IsPlayingStateEvent(val isPlaying: Boolean) : PlatformVideoEvent() { /** * Sent when audio tracks change. * - * This includes when the selected audio track changes after calling selectAudioTrack. Corresponds - * to ExoPlayer's onTracksChanged. + * This includes when the selected audio track changes after calling selectAudioTrack. + * Corresponds to ExoPlayer's onTracksChanged. * * Generated class from Pigeon that represents data sent in messages. */ -data class AudioTrackChangedEvent( - /** The ID of the newly selected audio track, if any. */ - val selectedTrackId: String? = null -) : PlatformVideoEvent() { +data class AudioTrackChangedEvent ( + /** The ID of the newly selected audio track, if any. */ + val selectedTrackId: String? = null +) : PlatformVideoEvent() + { companion object { fun fromList(pigeonVar_list: List): AudioTrackChangedEvent { val selectedTrackId = pigeonVar_list[0] as String? return AudioTrackChangedEvent(selectedTrackId) } } - fun toList(): List { return listOf( - selectedTrackId, + selectedTrackId, ) } - override fun equals(other: Any?): Boolean { if (other !is AudioTrackChangedEvent) { return false @@ -257,8 +261,7 @@ data class AudioTrackChangedEvent( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -268,20 +271,21 @@ data class AudioTrackChangedEvent( * * Generated class from Pigeon that represents data sent in messages. */ -data class PlatformVideoViewCreationParams(val playerId: Long) { +data class PlatformVideoViewCreationParams ( + val playerId: Long +) + { companion object { fun fromList(pigeonVar_list: List): PlatformVideoViewCreationParams { val playerId = pigeonVar_list[0] as Long return PlatformVideoViewCreationParams(playerId) } } - fun toList(): List { return listOf( - playerId, + playerId, ) } - override fun equals(other: Any?): Boolean { if (other !is PlatformVideoViewCreationParams) { return false @@ -289,19 +293,19 @@ data class PlatformVideoViewCreationParams(val playerId: Long) { if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } /** Generated class from Pigeon that represents data sent in messages. */ -data class CreationOptions( - val uri: String, - val formatHint: PlatformVideoFormat? = null, - val httpHeaders: Map, - val userAgent: String? = null -) { +data class CreationOptions ( + val uri: String, + val formatHint: PlatformVideoFormat? = null, + val httpHeaders: Map, + val userAgent: String? = null +) + { companion object { fun fromList(pigeonVar_list: List): CreationOptions { val uri = pigeonVar_list[0] as String @@ -311,16 +315,14 @@ data class CreationOptions( return CreationOptions(uri, formatHint, httpHeaders, userAgent) } } - fun toList(): List { return listOf( - uri, - formatHint, - httpHeaders, - userAgent, + uri, + formatHint, + httpHeaders, + userAgent, ) } - override fun equals(other: Any?): Boolean { if (other !is CreationOptions) { return false @@ -328,14 +330,17 @@ data class CreationOptions( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } /** Generated class from Pigeon that represents data sent in messages. */ -data class TexturePlayerIds(val playerId: Long, val textureId: Long) { +data class TexturePlayerIds ( + val playerId: Long, + val textureId: Long +) + { companion object { fun fromList(pigeonVar_list: List): TexturePlayerIds { val playerId = pigeonVar_list[0] as Long @@ -343,14 +348,12 @@ data class TexturePlayerIds(val playerId: Long, val textureId: Long) { return TexturePlayerIds(playerId, textureId) } } - fun toList(): List { return listOf( - playerId, - textureId, + playerId, + textureId, ) } - override fun equals(other: Any?): Boolean { if (other !is TexturePlayerIds) { return false @@ -358,19 +361,19 @@ data class TexturePlayerIds(val playerId: Long, val textureId: Long) { if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } /** Generated class from Pigeon that represents data sent in messages. */ -data class PlaybackState( - /** The current playback position, in milliseconds. */ - val playPosition: Long, - /** The current buffer position, in milliseconds. */ - val bufferPosition: Long -) { +data class PlaybackState ( + /** The current playback position, in milliseconds. */ + val playPosition: Long, + /** The current buffer position, in milliseconds. */ + val bufferPosition: Long +) + { companion object { fun fromList(pigeonVar_list: List): PlaybackState { val playPosition = pigeonVar_list[0] as Long @@ -378,14 +381,12 @@ data class PlaybackState( return PlaybackState(playPosition, bufferPosition) } } - fun toList(): List { return listOf( - playPosition, - bufferPosition, + playPosition, + bufferPosition, ) } - override fun equals(other: Any?): Boolean { if (other !is PlaybackState) { return false @@ -393,8 +394,7 @@ data class PlaybackState( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -404,16 +404,17 @@ data class PlaybackState( * * Generated class from Pigeon that represents data sent in messages. */ -data class AudioTrackMessage( - val id: String, - val label: String, - val language: String, - val isSelected: Boolean, - val bitrate: Long? = null, - val sampleRate: Long? = null, - val channelCount: Long? = null, - val codec: String? = null -) { +data class AudioTrackMessage ( + val id: String, + val label: String, + val language: String, + val isSelected: Boolean, + val bitrate: Long? = null, + val sampleRate: Long? = null, + val channelCount: Long? = null, + val codec: String? = null +) + { companion object { fun fromList(pigeonVar_list: List): AudioTrackMessage { val id = pigeonVar_list[0] as String @@ -424,24 +425,21 @@ data class AudioTrackMessage( val sampleRate = pigeonVar_list[5] as Long? val channelCount = pigeonVar_list[6] as Long? val codec = pigeonVar_list[7] as String? - return AudioTrackMessage( - id, label, language, isSelected, bitrate, sampleRate, channelCount, codec) + return AudioTrackMessage(id, label, language, isSelected, bitrate, sampleRate, channelCount, codec) } } - fun toList(): List { return listOf( - id, - label, - language, - isSelected, - bitrate, - sampleRate, - channelCount, - codec, + id, + label, + language, + isSelected, + bitrate, + sampleRate, + channelCount, + codec, ) } - override fun equals(other: Any?): Boolean { if (other !is AudioTrackMessage) { return false @@ -449,8 +447,7 @@ data class AudioTrackMessage( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -460,17 +457,18 @@ data class AudioTrackMessage( * * Generated class from Pigeon that represents data sent in messages. */ -data class ExoPlayerAudioTrackData( - val groupIndex: Long, - val trackIndex: Long, - val label: String? = null, - val language: String? = null, - val isSelected: Boolean, - val bitrate: Long? = null, - val sampleRate: Long? = null, - val channelCount: Long? = null, - val codec: String? = null -) { +data class ExoPlayerAudioTrackData ( + val groupIndex: Long, + val trackIndex: Long, + val label: String? = null, + val language: String? = null, + val isSelected: Boolean, + val bitrate: Long? = null, + val sampleRate: Long? = null, + val channelCount: Long? = null, + val codec: String? = null +) + { companion object { fun fromList(pigeonVar_list: List): ExoPlayerAudioTrackData { val groupIndex = pigeonVar_list[0] as Long @@ -482,33 +480,22 @@ data class ExoPlayerAudioTrackData( val sampleRate = pigeonVar_list[6] as Long? val channelCount = pigeonVar_list[7] as Long? val codec = pigeonVar_list[8] as String? - return ExoPlayerAudioTrackData( - groupIndex, - trackIndex, - label, - language, - isSelected, - bitrate, - sampleRate, - channelCount, - codec) + return ExoPlayerAudioTrackData(groupIndex, trackIndex, label, language, isSelected, bitrate, sampleRate, channelCount, codec) } } - fun toList(): List { return listOf( - groupIndex, - trackIndex, - label, - language, - isSelected, - bitrate, - sampleRate, - channelCount, - codec, + groupIndex, + trackIndex, + label, + language, + isSelected, + bitrate, + sampleRate, + channelCount, + codec, ) } - override fun equals(other: Any?): Boolean { if (other !is ExoPlayerAudioTrackData) { return false @@ -516,8 +503,7 @@ data class ExoPlayerAudioTrackData( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } @@ -527,23 +513,22 @@ data class ExoPlayerAudioTrackData( * * Generated class from Pigeon that represents data sent in messages. */ -data class NativeAudioTrackData( - /** ExoPlayer-based tracks */ - val exoPlayerTracks: List? = null -) { +data class NativeAudioTrackData ( + /** ExoPlayer-based tracks */ + val exoPlayerTracks: List? = null +) + { companion object { fun fromList(pigeonVar_list: List): NativeAudioTrackData { val exoPlayerTracks = pigeonVar_list[0] as List? return NativeAudioTrackData(exoPlayerTracks) } } - fun toList(): List { return listOf( - exoPlayerTracks, + exoPlayerTracks, ) } - override fun equals(other: Any?): Boolean { if (other !is NativeAudioTrackData) { return false @@ -551,32 +536,42 @@ data class NativeAudioTrackData( if (this === other) { return true } - return MessagesPigeonUtils.deepEquals(toList(), other.toList()) - } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } override fun hashCode(): Int = toList().hashCode() } - private open class MessagesPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { 129.toByte() -> { - return (readValue(buffer) as Long?)?.let { PlatformVideoFormat.ofRaw(it.toInt()) } + return (readValue(buffer) as Long?)?.let { + PlatformVideoFormat.ofRaw(it.toInt()) + } } 130.toByte() -> { - return (readValue(buffer) as Long?)?.let { PlatformPlaybackState.ofRaw(it.toInt()) } + return (readValue(buffer) as Long?)?.let { + PlatformPlaybackState.ofRaw(it.toInt()) + } } 131.toByte() -> { - return (readValue(buffer) as? List)?.let { InitializationEvent.fromList(it) } + return (readValue(buffer) as? List)?.let { + InitializationEvent.fromList(it) + } } 132.toByte() -> { - return (readValue(buffer) as? List)?.let { PlaybackStateChangeEvent.fromList(it) } + return (readValue(buffer) as? List)?.let { + PlaybackStateChangeEvent.fromList(it) + } } 133.toByte() -> { - return (readValue(buffer) as? List)?.let { IsPlayingStateEvent.fromList(it) } + return (readValue(buffer) as? List)?.let { + IsPlayingStateEvent.fromList(it) + } } 134.toByte() -> { - return (readValue(buffer) as? List)?.let { AudioTrackChangedEvent.fromList(it) } + return (readValue(buffer) as? List)?.let { + AudioTrackChangedEvent.fromList(it) + } } 135.toByte() -> { return (readValue(buffer) as? List)?.let { @@ -584,28 +579,39 @@ private open class MessagesPigeonCodec : StandardMessageCodec() { } } 136.toByte() -> { - return (readValue(buffer) as? List)?.let { CreationOptions.fromList(it) } + return (readValue(buffer) as? List)?.let { + CreationOptions.fromList(it) + } } 137.toByte() -> { - return (readValue(buffer) as? List)?.let { TexturePlayerIds.fromList(it) } + return (readValue(buffer) as? List)?.let { + TexturePlayerIds.fromList(it) + } } 138.toByte() -> { - return (readValue(buffer) as? List)?.let { PlaybackState.fromList(it) } + return (readValue(buffer) as? List)?.let { + PlaybackState.fromList(it) + } } 139.toByte() -> { - return (readValue(buffer) as? List)?.let { AudioTrackMessage.fromList(it) } + return (readValue(buffer) as? List)?.let { + AudioTrackMessage.fromList(it) + } } 140.toByte() -> { - return (readValue(buffer) as? List)?.let { ExoPlayerAudioTrackData.fromList(it) } + return (readValue(buffer) as? List)?.let { + ExoPlayerAudioTrackData.fromList(it) + } } 141.toByte() -> { - return (readValue(buffer) as? List)?.let { NativeAudioTrackData.fromList(it) } + return (readValue(buffer) as? List)?.let { + NativeAudioTrackData.fromList(it) + } } else -> super.readValueOfType(type, buffer) } } - - override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { when (value) { is PlatformVideoFormat -> { stream.write(129) @@ -669,47 +675,31 @@ val MessagesPigeonMethodCodec = StandardMethodCodec(MessagesPigeonCodec()) /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface AndroidVideoPlayerApi { fun initialize() - fun createForPlatformView(options: CreationOptions): Long - fun createForTextureView(options: CreationOptions): TexturePlayerIds - fun dispose(playerId: Long) - fun setMixWithOthers(mixWithOthers: Boolean) - fun getLookupKeyForAsset(asset: String, packageName: String?): String companion object { /** The codec used by AndroidVideoPlayerApi. */ - val codec: MessageCodec by lazy { MessagesPigeonCodec() } - /** - * Sets up an instance of `AndroidVideoPlayerApi` to handle messages through the - * `binaryMessenger`. - */ + val codec: MessageCodec by lazy { + MessagesPigeonCodec() + } + /** Sets up an instance of `AndroidVideoPlayerApi` to handle messages through the `binaryMessenger`. */ @JvmOverloads - fun setUp( - binaryMessenger: BinaryMessenger, - api: AndroidVideoPlayerApi?, - messageChannelSuffix: String = "" - ) { - val separatedMessageChannelSuffix = - if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + fun setUp(binaryMessenger: BinaryMessenger, api: AndroidVideoPlayerApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.initialize$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.initialize$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - api.initialize() - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.initialize() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -717,21 +707,16 @@ interface AndroidVideoPlayerApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.createForPlatformView$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.createForPlatformView$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val optionsArg = args[0] as CreationOptions - val wrapped: List = - try { - listOf(api.createForPlatformView(optionsArg)) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.createForPlatformView(optionsArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -739,21 +724,16 @@ interface AndroidVideoPlayerApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.createForTextureView$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.createForTextureView$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val optionsArg = args[0] as CreationOptions - val wrapped: List = - try { - listOf(api.createForTextureView(optionsArg)) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.createForTextureView(optionsArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -761,22 +741,17 @@ interface AndroidVideoPlayerApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.dispose$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.dispose$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val playerIdArg = args[0] as Long - val wrapped: List = - try { - api.dispose(playerIdArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.dispose(playerIdArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -784,22 +759,17 @@ interface AndroidVideoPlayerApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val mixWithOthersArg = args[0] as Boolean - val wrapped: List = - try { - api.setMixWithOthers(mixWithOthersArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.setMixWithOthers(mixWithOthersArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -807,22 +777,17 @@ interface AndroidVideoPlayerApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.getLookupKeyForAsset$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.getLookupKeyForAsset$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val assetArg = args[0] as String val packageNameArg = args[1] as String? - val wrapped: List = - try { - listOf(api.getLookupKeyForAsset(assetArg, packageNameArg)) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.getLookupKeyForAsset(assetArg, packageNameArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -854,39 +819,41 @@ interface VideoPlayerInstanceApi { fun getAudioTracks(): NativeAudioTrackData /** Selects which audio track is chosen for playback from its [groupIndex] and [trackIndex] */ fun selectAudioTrack(groupIndex: Long, trackIndex: Long) + /** + * Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. + * Pass 0 to remove any bandwidth limit and allow the player to select quality freely. + * Common values: + * - 360p: 500000 bps (500 kbps) + * - 480p: 800000 bps (800 kbps) + * - 720p: 1200000 bps (1.2 Mbps) + * - 1080p: 2500000 bps (2.5 Mbps) + * + * Note: This helps ExoPlayer's track selector choose appropriate quality variants + * for HLS streams. The player will attempt to stay within this bandwidth estimate. + */ + fun setBandwidthLimit(maxBandwidthBps: Long) companion object { /** The codec used by VideoPlayerInstanceApi. */ - val codec: MessageCodec by lazy { MessagesPigeonCodec() } - /** - * Sets up an instance of `VideoPlayerInstanceApi` to handle messages through the - * `binaryMessenger`. - */ + val codec: MessageCodec by lazy { + MessagesPigeonCodec() + } + /** Sets up an instance of `VideoPlayerInstanceApi` to handle messages through the `binaryMessenger`. */ @JvmOverloads - fun setUp( - binaryMessenger: BinaryMessenger, - api: VideoPlayerInstanceApi?, - messageChannelSuffix: String = "" - ) { - val separatedMessageChannelSuffix = - if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + fun setUp(binaryMessenger: BinaryMessenger, api: VideoPlayerInstanceApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setLooping$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setLooping$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val loopingArg = args[0] as Boolean - val wrapped: List = - try { - api.setLooping(loopingArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.setLooping(loopingArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -894,22 +861,17 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setVolume$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setVolume$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val volumeArg = args[0] as Double - val wrapped: List = - try { - api.setVolume(volumeArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.setVolume(volumeArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -917,22 +879,17 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setPlaybackSpeed$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setPlaybackSpeed$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val speedArg = args[0] as Double - val wrapped: List = - try { - api.setPlaybackSpeed(speedArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.setPlaybackSpeed(speedArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -940,20 +897,15 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.play$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.play$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - api.play() - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.play() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -961,20 +913,15 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - api.pause() - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.pause() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -982,22 +929,17 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val positionArg = args[0] as Long - val wrapped: List = - try { - api.seekTo(positionArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.seekTo(positionArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1005,19 +947,14 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getCurrentPosition$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getCurrentPosition$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - listOf(api.getCurrentPosition()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.getCurrentPosition()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1025,19 +962,14 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getBufferedPosition$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getBufferedPosition$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - listOf(api.getBufferedPosition()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.getBufferedPosition()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1045,19 +977,14 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getAudioTracks$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getAudioTracks$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { _, reply -> - val wrapped: List = - try { - listOf(api.getAudioTracks()) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + listOf(api.getAudioTracks()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1065,23 +992,36 @@ interface VideoPlayerInstanceApi { } } run { - val channel = - BasicMessageChannel( - binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.selectAudioTrack$separatedMessageChannelSuffix", - codec) + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.selectAudioTrack$separatedMessageChannelSuffix", codec) if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List val groupIndexArg = args[0] as Long val trackIndexArg = args[1] as Long - val wrapped: List = - try { - api.selectAudioTrack(groupIndexArg, trackIndexArg) - listOf(null) - } catch (exception: Throwable) { - MessagesPigeonUtils.wrapError(exception) - } + val wrapped: List = try { + api.selectAudioTrack(groupIndexArg, trackIndexArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setBandwidthLimit$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val maxBandwidthBpsArg = args[0] as Long + val wrapped: List = try { + api.setBandwidthLimit(maxBandwidthBpsArg) + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } reply.reply(wrapped) } } else { @@ -1092,8 +1032,9 @@ interface VideoPlayerInstanceApi { } } -private class MessagesPigeonStreamHandler(val wrapper: MessagesPigeonEventChannelWrapper) : - EventChannel.StreamHandler { +private class MessagesPigeonStreamHandler( + val wrapper: MessagesPigeonEventChannelWrapper +) : EventChannel.StreamHandler { var pigeonSink: PigeonEventSink? = null override fun onListen(p0: Any?, sink: EventChannel.EventSink) { @@ -1126,26 +1067,21 @@ class PigeonEventSink(private val sink: EventChannel.EventSink) { sink.endOfStream() } } - + abstract class VideoEventsStreamHandler : MessagesPigeonEventChannelWrapper { companion object { - fun register( - messenger: BinaryMessenger, - streamHandler: VideoEventsStreamHandler, - instanceName: String = "" - ) { - var channelName: String = - "dev.flutter.pigeon.video_player_android.VideoEventChannel.videoEvents" + fun register(messenger: BinaryMessenger, streamHandler: VideoEventsStreamHandler, instanceName: String = "") { + var channelName: String = "dev.flutter.pigeon.video_player_android.VideoEventChannel.videoEvents" if (instanceName.isNotEmpty()) { channelName += ".$instanceName" } val internalStreamHandler = MessagesPigeonStreamHandler(streamHandler) - EventChannel(messenger, channelName, MessagesPigeonMethodCodec) - .setStreamHandler(internalStreamHandler) + EventChannel(messenger, channelName, MessagesPigeonMethodCodec).setStreamHandler(internalStreamHandler) } } - // Implement methods from MessagesPigeonEventChannelWrapper - override fun onListen(p0: Any?, sink: PigeonEventSink) {} +// Implement methods from MessagesPigeonEventChannelWrapper +override fun onListen(p0: Any?, sink: PigeonEventSink) {} - override fun onCancel(p0: Any?) {} +override fun onCancel(p0: Any?) {} } + 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 84249bd41afd..eddaadcc894c 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 @@ -266,6 +266,11 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { return true; } + @override + Future setBandwidthLimit(int playerId, int maxBandwidthBps) { + return _playerWith(id: playerId).setBandwidthLimit(maxBandwidthBps); + } + _PlayerInstance _playerWith({required int id}) { final _PlayerInstance? player = _players[id]; return player ?? (throw StateError('No active player with ID $id.')); @@ -384,6 +389,10 @@ class _PlayerInstance { } } + Future setBandwidthLimit(int maxBandwidthBps) { + return _api.setBandwidthLimit(maxBandwidthBps); + } + Future dispose() async { _isDisposed = true; _bufferPollingTimer?.cancel(); 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 1aca7dc531dd..93186880ca7c 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 // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.5), do not edit directly. +// Autogenerated from Pigeon (v26.1.7), 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, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -1142,6 +1142,41 @@ class VideoPlayerInstanceApi { return; } } + + /// Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. + /// Pass 0 to remove any bandwidth limit and allow the player to select quality freely. + /// Common values: + /// - 360p: 500000 bps (500 kbps) + /// - 480p: 800000 bps (800 kbps) + /// - 720p: 1200000 bps (1.2 Mbps) + /// - 1080p: 2500000 bps (2.5 Mbps) + /// + /// Note: This helps ExoPlayer's track selector choose appropriate quality variants + /// for HLS streams. The player will attempt to stay within this bandwidth estimate. + Future setBandwidthLimit(int maxBandwidthBps) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setBandwidthLimit$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [maxBandwidthBps], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture 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; + } + } } Stream videoEvents({String instanceName = ''}) { diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart index 8666b074969a..24a6446f7c3e 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -192,6 +192,18 @@ abstract class VideoPlayerInstanceApi { /// Selects which audio track is chosen for playback from its [groupIndex] and [trackIndex] void selectAudioTrack(int groupIndex, int trackIndex); + + /// Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. + /// Pass 0 to remove any bandwidth limit and allow the player to select quality freely. + /// Common values: + /// - 360p: 500000 bps (500 kbps) + /// - 480p: 800000 bps (800 kbps) + /// - 720p: 1200000 bps (1.2 Mbps) + /// - 1080p: 2500000 bps (2.5 Mbps) + /// + /// Note: This helps ExoPlayer's track selector choose appropriate quality variants + /// for HLS streams. The player will attempt to stay within this bandwidth estimate. + void setBandwidthLimit(int maxBandwidthBps); } @EventChannelApi() diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index 1e8ff4050e29..979f57eee7d5 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +* Automatic Bitrate Streaming support to iOS. + ## 2.9.0 * Implements `getAudioTracks()` and `selectAudioTrack()` methods. 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 5b79d4291c24..0286956447fa 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 @@ -507,6 +507,23 @@ - (void)selectAudioTrackAtIndex:(NSInteger)trackIndex } } +- (void)setBandwidthLimit:(NSInteger)maxBandwidthBps + error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { + AVPlayerItem *currentItem = _player.currentItem; + NSAssert(currentItem, @"currentItem should not be nil"); + + if (maxBandwidthBps <= 0) { + // Remove the bandwidth limit by setting to the maximum value + // This allows AVPlayer to select any available quality + currentItem.preferredPeakBitRate = INFINITY; + } else { + // Set the preferred peak bitrate in bits per second + // AVPlayer will attempt to play video at or below this bitrate + // Note: The actual bitrate selected depends on the available HLS variants + currentItem.preferredPeakBitRate = (double)maxBandwidthBps; + } +} + #pragma mark - Private - (int64_t)duration { 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 01f584187b11..b3e8ea622b5c 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,10 +1,10 @@ // Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// Autogenerated from Pigeon (v26.1.7), do not edit directly. // See also: https://pub.dev/packages/pigeon -#import +@import Foundation; @protocol FlutterBinaryMessenger; @protocol FlutterMessageCodec; @@ -22,41 +22,42 @@ NS_ASSUME_NONNULL_BEGIN @interface FVPPlatformVideoViewCreationParams : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithPlayerId:(NSInteger)playerId; -@property(nonatomic, assign) NSInteger playerId; ++ (instancetype)makeWithPlayerId:(NSInteger )playerId; +@property(nonatomic, assign) NSInteger playerId; @end @interface FVPCreationOptions : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithUri:(NSString *)uri - httpHeaders:(NSDictionary *)httpHeaders; -@property(nonatomic, copy) NSString *uri; -@property(nonatomic, copy) NSDictionary *httpHeaders; + httpHeaders:(NSDictionary *)httpHeaders; +@property(nonatomic, copy) NSString * uri; +@property(nonatomic, copy) NSDictionary * httpHeaders; @end @interface FVPTexturePlayerIds : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithPlayerId:(NSInteger)playerId textureId:(NSInteger)textureId; -@property(nonatomic, assign) NSInteger playerId; -@property(nonatomic, assign) NSInteger textureId; ++ (instancetype)makeWithPlayerId:(NSInteger )playerId + textureId:(NSInteger )textureId; +@property(nonatomic, assign) NSInteger playerId; +@property(nonatomic, assign) NSInteger textureId; @end /// Raw audio track data from AVMediaSelectionOption (for HLS streams). @interface FVPMediaSelectionAudioTrackData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithIndex:(NSInteger)index - displayName:(nullable NSString *)displayName - languageCode:(nullable NSString *)languageCode - isSelected:(BOOL)isSelected - commonMetadataTitle:(nullable NSString *)commonMetadataTitle; -@property(nonatomic, assign) NSInteger index; -@property(nonatomic, copy, nullable) NSString *displayName; -@property(nonatomic, copy, nullable) NSString *languageCode; -@property(nonatomic, assign) BOOL isSelected; -@property(nonatomic, copy, nullable) NSString *commonMetadataTitle; ++ (instancetype)makeWithIndex:(NSInteger )index + displayName:(nullable NSString *)displayName + languageCode:(nullable NSString *)languageCode + isSelected:(BOOL )isSelected + commonMetadataTitle:(nullable NSString *)commonMetadataTitle; +@property(nonatomic, assign) NSInteger index; +@property(nonatomic, copy, nullable) NSString * displayName; +@property(nonatomic, copy, nullable) NSString * languageCode; +@property(nonatomic, assign) BOOL isSelected; +@property(nonatomic, copy, nullable) NSString * commonMetadataTitle; @end /// The codec used by all APIs. @@ -65,25 +66,17 @@ NSObject *FVPGetMessagesCodec(void); @protocol FVPAVFoundationVideoPlayerApi - (void)initialize:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)createPlatformViewPlayerWithOptions:(FVPCreationOptions *)params - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSNumber *)createPlatformViewPlayerWithOptions:(FVPCreationOptions *)params error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. -- (nullable FVPTexturePlayerIds *) - createTexturePlayerWithOptions:(FVPCreationOptions *)creationOptions - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable FVPTexturePlayerIds *)createTexturePlayerWithOptions:(FVPCreationOptions *)creationOptions error:(FlutterError *_Nullable *_Nonnull)error; - (void)setMixWithOthers:(BOOL)mixWithOthers error:(FlutterError *_Nullable *_Nonnull)error; -- (nullable NSString *)fileURLForAssetWithName:(NSString *)asset - package:(nullable NSString *)package - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSString *)fileURLForAssetWithName:(NSString *)asset package:(nullable NSString *)package error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void SetUpFVPAVFoundationVideoPlayerApi( - id binaryMessenger, - NSObject *_Nullable api); +extern void SetUpFVPAVFoundationVideoPlayerApi(id binaryMessenger, NSObject *_Nullable api); + +extern void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id binaryMessenger, NSObject *_Nullable api, NSString *messageChannelSuffix); -extern void SetUpFVPAVFoundationVideoPlayerApiWithSuffix( - id binaryMessenger, - NSObject *_Nullable api, NSString *messageChannelSuffix); @protocol FVPVideoPlayerInstanceApi - (void)setLooping:(BOOL)looping error:(FlutterError *_Nullable *_Nonnull)error; @@ -96,17 +89,23 @@ extern void SetUpFVPAVFoundationVideoPlayerApiWithSuffix( - (void)pauseWithError:(FlutterError *_Nullable *_Nonnull)error; - (void)disposeWithError:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. -- (nullable NSArray *)getAudioTracks: - (FlutterError *_Nullable *_Nonnull)error; -- (void)selectAudioTrackAtIndex:(NSInteger)trackIndex - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSArray *)getAudioTracks:(FlutterError *_Nullable *_Nonnull)error; +- (void)selectAudioTrackAtIndex:(NSInteger)trackIndex error:(FlutterError *_Nullable *_Nonnull)error; +/// Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. +/// Pass 0 to remove any bandwidth limit and allow the player to select quality freely. +/// Common values: +/// - 360p: 500000 bps (500 kbps) +/// - 480p: 800000 bps (800 kbps) +/// - 720p: 1200000 bps (1.2 Mbps) +/// - 1080p: 2500000 bps (2.5 Mbps) +/// +/// Note: On iOS/macOS, this sets the preferredPeakBitRate on AVPlayerItem, +/// which influences AVPlayer's HLS variant selection. +- (void)setBandwidthLimit:(NSInteger)maxBandwidthBps error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, - NSObject *_Nullable api); +extern void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, NSObject *_Nullable api); -extern void SetUpFVPVideoPlayerInstanceApiWithSuffix( - id binaryMessenger, NSObject *_Nullable api, - NSString *messageChannelSuffix); +extern void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryMessenger, NSObject *_Nullable api, NSString *messageChannelSuffix); NS_ASSUME_NONNULL_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 c421576564cf..7539773feb0c 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,19 +1,15 @@ // Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// Autogenerated from Pigeon (v26.1.7), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "./include/video_player_avfoundation/messages.g.h" #if TARGET_OS_OSX -#import +@import FlutterMacOS; #else -#import -#endif - -#if !__has_feature(objc_arc) -#error File requires ARC to be enabled. +@import Flutter; #endif static NSArray *wrapResult(id result, FlutterError *error) { @@ -55,15 +51,13 @@ + (nullable FVPMediaSelectionAudioTrackData *)nullableFromList:(NSArray *)li @end @implementation FVPPlatformVideoViewCreationParams -+ (instancetype)makeWithPlayerId:(NSInteger)playerId { - FVPPlatformVideoViewCreationParams *pigeonResult = - [[FVPPlatformVideoViewCreationParams alloc] init]; ++ (instancetype)makeWithPlayerId:(NSInteger )playerId { + FVPPlatformVideoViewCreationParams* pigeonResult = [[FVPPlatformVideoViewCreationParams alloc] init]; pigeonResult.playerId = playerId; return pigeonResult; } + (FVPPlatformVideoViewCreationParams *)fromList:(NSArray *)list { - FVPPlatformVideoViewCreationParams *pigeonResult = - [[FVPPlatformVideoViewCreationParams alloc] init]; + FVPPlatformVideoViewCreationParams *pigeonResult = [[FVPPlatformVideoViewCreationParams alloc] init]; pigeonResult.playerId = [GetNullableObjectAtIndex(list, 0) integerValue]; return pigeonResult; } @@ -79,8 +73,8 @@ + (nullable FVPPlatformVideoViewCreationParams *)nullableFromList:(NSArray * @implementation FVPCreationOptions + (instancetype)makeWithUri:(NSString *)uri - httpHeaders:(NSDictionary *)httpHeaders { - FVPCreationOptions *pigeonResult = [[FVPCreationOptions alloc] init]; + httpHeaders:(NSDictionary *)httpHeaders { + FVPCreationOptions* pigeonResult = [[FVPCreationOptions alloc] init]; pigeonResult.uri = uri; pigeonResult.httpHeaders = httpHeaders; return pigeonResult; @@ -103,8 +97,9 @@ + (nullable FVPCreationOptions *)nullableFromList:(NSArray *)list { @end @implementation FVPTexturePlayerIds -+ (instancetype)makeWithPlayerId:(NSInteger)playerId textureId:(NSInteger)textureId { - FVPTexturePlayerIds *pigeonResult = [[FVPTexturePlayerIds alloc] init]; ++ (instancetype)makeWithPlayerId:(NSInteger )playerId + textureId:(NSInteger )textureId { + FVPTexturePlayerIds* pigeonResult = [[FVPTexturePlayerIds alloc] init]; pigeonResult.playerId = playerId; pigeonResult.textureId = textureId; return pigeonResult; @@ -127,12 +122,12 @@ + (nullable FVPTexturePlayerIds *)nullableFromList:(NSArray *)list { @end @implementation FVPMediaSelectionAudioTrackData -+ (instancetype)makeWithIndex:(NSInteger)index - displayName:(nullable NSString *)displayName - languageCode:(nullable NSString *)languageCode - isSelected:(BOOL)isSelected - commonMetadataTitle:(nullable NSString *)commonMetadataTitle { - FVPMediaSelectionAudioTrackData *pigeonResult = [[FVPMediaSelectionAudioTrackData alloc] init]; ++ (instancetype)makeWithIndex:(NSInteger )index + displayName:(nullable NSString *)displayName + languageCode:(nullable NSString *)languageCode + isSelected:(BOOL )isSelected + commonMetadataTitle:(nullable NSString *)commonMetadataTitle { + FVPMediaSelectionAudioTrackData* pigeonResult = [[FVPMediaSelectionAudioTrackData alloc] init]; pigeonResult.index = index; pigeonResult.displayName = displayName; pigeonResult.languageCode = languageCode; @@ -168,13 +163,13 @@ @interface FVPMessagesPigeonCodecReader : FlutterStandardReader @implementation FVPMessagesPigeonCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { - case 129: + case 129: return [FVPPlatformVideoViewCreationParams fromList:[self readValue]]; - case 130: + case 130: return [FVPCreationOptions fromList:[self readValue]]; - case 131: + case 131: return [FVPTexturePlayerIds fromList:[self readValue]]; - case 132: + case 132: return [FVPMediaSelectionAudioTrackData fromList:[self readValue]]; default: return [super readValueOfType:type]; @@ -219,35 +214,25 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ - FVPMessagesPigeonCodecReaderWriter *readerWriter = - [[FVPMessagesPigeonCodecReaderWriter alloc] init]; + FVPMessagesPigeonCodecReaderWriter *readerWriter = [[FVPMessagesPigeonCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } -void SetUpFVPAVFoundationVideoPlayerApi(id binaryMessenger, - NSObject *api) { +void SetUpFVPAVFoundationVideoPlayerApi(id binaryMessenger, NSObject *api) { SetUpFVPAVFoundationVideoPlayerApiWithSuffix(binaryMessenger, api, @""); } -void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id binaryMessenger, - NSObject *api, - NSString *messageChannelSuffix) { - messageChannelSuffix = messageChannelSuffix.length > 0 - ? [NSString stringWithFormat:@".%@", messageChannelSuffix] - : @""; +void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id binaryMessenger, NSObject *api, NSString *messageChannelSuffix) { + messageChannelSuffix = messageChannelSuffix.length > 0 ? [NSString stringWithFormat: @".%@", messageChannelSuffix] : @""; { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.initialize", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.initialize", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(initialize:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(initialize:)", - api); + NSCAssert([api respondsToSelector:@selector(initialize:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(initialize:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; [api initialize:&error]; @@ -258,19 +243,13 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString - stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.createForPlatformView", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForPlatformView", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(createPlatformViewPlayerWithOptions:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(createPlatformViewPlayerWithOptions:error:)", - api); + NSCAssert([api respondsToSelector:@selector(createPlatformViewPlayerWithOptions:error:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(createPlatformViewPlayerWithOptions:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FVPCreationOptions *arg_params = GetNullableObjectAtIndex(args, 0); @@ -283,25 +262,18 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString - stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.createForTextureView", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForTextureView", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(createTexturePlayerWithOptions:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(createTexturePlayerWithOptions:error:)", - api); + NSCAssert([api respondsToSelector:@selector(createTexturePlayerWithOptions:error:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(createTexturePlayerWithOptions:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FVPCreationOptions *arg_creationOptions = GetNullableObjectAtIndex(args, 0); FlutterError *error; - FVPTexturePlayerIds *output = [api createTexturePlayerWithOptions:arg_creationOptions - error:&error]; + FVPTexturePlayerIds *output = [api createTexturePlayerWithOptions:arg_creationOptions error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -309,18 +281,13 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.setMixWithOthers", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(setMixWithOthers:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(setMixWithOthers:error:)", - api); + NSCAssert([api respondsToSelector:@selector(setMixWithOthers:error:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(setMixWithOthers:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; BOOL arg_mixWithOthers = [GetNullableObjectAtIndex(args, 0) boolValue]; @@ -333,18 +300,13 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.getAssetUrl", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getAssetUrl", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(fileURLForAssetWithName:package:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(fileURLForAssetWithName:package:error:)", - api); + NSCAssert([api respondsToSelector:@selector(fileURLForAssetWithName:package:error:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(fileURLForAssetWithName:package:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_asset = GetNullableObjectAtIndex(args, 0); @@ -358,30 +320,20 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin } } } -void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, - NSObject *api) { +void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, NSObject *api) { SetUpFVPVideoPlayerInstanceApiWithSuffix(binaryMessenger, api, @""); } -void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryMessenger, - NSObject *api, - NSString *messageChannelSuffix) { - messageChannelSuffix = messageChannelSuffix.length > 0 - ? [NSString stringWithFormat:@".%@", messageChannelSuffix] - : @""; +void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryMessenger, NSObject *api, NSString *messageChannelSuffix) { + messageChannelSuffix = messageChannelSuffix.length > 0 ? [NSString stringWithFormat: @".%@", messageChannelSuffix] : @""; { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.setLooping", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setLooping", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(setLooping:error:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(setLooping:error:)", - api); + NSCAssert([api respondsToSelector:@selector(setLooping:error:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(setLooping:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; BOOL arg_looping = [GetNullableObjectAtIndex(args, 0) boolValue]; @@ -394,18 +346,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.setVolume", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setVolume", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(setVolume:error:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(setVolume:error:)", - api); + NSCAssert([api respondsToSelector:@selector(setVolume:error:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(setVolume:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; double arg_volume = [GetNullableObjectAtIndex(args, 0) doubleValue]; @@ -418,18 +365,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.setPlaybackSpeed", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setPlaybackSpeed", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(setPlaybackSpeed:error:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to " - @"@selector(setPlaybackSpeed:error:)", - api); + NSCAssert([api respondsToSelector:@selector(setPlaybackSpeed:error:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(setPlaybackSpeed:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; double arg_speed = [GetNullableObjectAtIndex(args, 0) doubleValue]; @@ -442,17 +384,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.play", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.play", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(playWithError:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(playWithError:)", - api); + NSCAssert([api respondsToSelector:@selector(playWithError:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(playWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; [api playWithError:&error]; @@ -463,16 +401,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.getPosition", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getPosition", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(position:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(position:)", api); + NSCAssert([api respondsToSelector:@selector(position:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(position:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; NSNumber *output = [api position:&error]; @@ -483,42 +418,32 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.seekTo", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.seekTo", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(seekTo:completion:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(seekTo:completion:)", - api); + NSCAssert([api respondsToSelector:@selector(seekTo:completion:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(seekTo:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSInteger arg_position = [GetNullableObjectAtIndex(args, 0) integerValue]; - [api seekTo:arg_position - completion:^(FlutterError *_Nullable error) { - callback(wrapResult(nil, error)); - }]; + [api seekTo:arg_position completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; }]; } else { [channel setMessageHandler:nil]; } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.pause", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.pause", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(pauseWithError:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(pauseWithError:)", - api); + NSCAssert([api respondsToSelector:@selector(pauseWithError:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(pauseWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; [api pauseWithError:&error]; @@ -529,18 +454,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.dispose", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.dispose", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(disposeWithError:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(disposeWithError:)", - api); + NSCAssert([api respondsToSelector:@selector(disposeWithError:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(disposeWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; [api disposeWithError:&error]; @@ -551,17 +471,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.getAudioTracks", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getAudioTracks", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(getAudioTracks:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(getAudioTracks:)", - api); + NSCAssert([api respondsToSelector:@selector(getAudioTracks:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(getAudioTracks:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; NSArray *output = [api getAudioTracks:&error]; @@ -572,18 +488,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.selectAudioTrack", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.selectAudioTrack", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(selectAudioTrackAtIndex:error:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to " - @"@selector(selectAudioTrackAtIndex:error:)", - api); + NSCAssert([api respondsToSelector:@selector(selectAudioTrackAtIndex:error:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(selectAudioTrackAtIndex:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSInteger arg_trackIndex = [GetNullableObjectAtIndex(args, 0) integerValue]; @@ -595,4 +506,33 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM [channel setMessageHandler:nil]; } } + /// Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. + /// Pass 0 to remove any bandwidth limit and allow the player to select quality freely. + /// Common values: + /// - 360p: 500000 bps (500 kbps) + /// - 480p: 800000 bps (800 kbps) + /// - 720p: 1200000 bps (1.2 Mbps) + /// - 1080p: 2500000 bps (2.5 Mbps) + /// + /// Note: On iOS/macOS, this sets the preferredPeakBitRate on AVPlayerItem, + /// which influences AVPlayer's HLS variant selection. + { + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setBandwidthLimit", messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FVPGetMessagesCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(setBandwidthLimit:error:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(setBandwidthLimit:error:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSInteger arg_maxBandwidthBps = [GetNullableObjectAtIndex(args, 0) integerValue]; + FlutterError *error; + [api setBandwidthLimit:arg_maxBandwidthBps error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } } 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 6684d9c4c658..b4881a293aa2 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 @@ -212,6 +212,11 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { return true; } + @override + Future setBandwidthLimit(int playerId, int maxBandwidthBps) { + return _playerWith(id: playerId).setBandwidthLimit(maxBandwidthBps); + } + @override Widget buildView(int playerId) { return buildViewWithOptions(VideoViewOptions(playerId: playerId)); @@ -289,6 +294,9 @@ class _PlayerInstance { Future selectAudioTrack(int trackIndex) => _api.selectAudioTrack(trackIndex); + Future setBandwidthLimit(int maxBandwidthBps) => + _api.setBandwidthLimit(maxBandwidthBps); + Stream get videoEvents { _eventSubscription ??= _eventChannel.receiveBroadcastStream().listen( _onStreamEvent, 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 76b66c3ca4bf..ffacdb136b0e 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,9 +1,9 @@ // Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// Autogenerated from Pigeon (v26.1.7), 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 +// 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, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers import 'dart:async'; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; @@ -276,17 +276,15 @@ class AVFoundationVideoPlayerApi { final String pigeonVar_messageChannelSuffix; Future initialize() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.initialize$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -301,19 +299,17 @@ class AVFoundationVideoPlayerApi { } Future createForPlatformView(CreationOptions params) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForPlatformView$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [params], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -335,19 +331,17 @@ class AVFoundationVideoPlayerApi { Future createForTextureView( CreationOptions creationOptions, ) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForTextureView$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [creationOptions], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -367,19 +361,17 @@ class AVFoundationVideoPlayerApi { } Future setMixWithOthers(bool mixWithOthers) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [mixWithOthers], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -394,19 +386,17 @@ class AVFoundationVideoPlayerApi { } Future getAssetUrl(String asset, String? package) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getAssetUrl$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [asset, package], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -439,19 +429,17 @@ class VideoPlayerInstanceApi { final String pigeonVar_messageChannelSuffix; Future setLooping(bool looping) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setLooping$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [looping], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -466,19 +454,17 @@ class VideoPlayerInstanceApi { } Future setVolume(double volume) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setVolume$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [volume], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -493,19 +479,17 @@ class VideoPlayerInstanceApi { } Future setPlaybackSpeed(double speed) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setPlaybackSpeed$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [speed], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -520,17 +504,15 @@ class VideoPlayerInstanceApi { } Future play() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.play$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -545,17 +527,15 @@ class VideoPlayerInstanceApi { } Future getPosition() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getPosition$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -575,19 +555,17 @@ class VideoPlayerInstanceApi { } Future seekTo(int position) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.seekTo$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [position], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -602,17 +580,15 @@ class VideoPlayerInstanceApi { } Future pause() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.pause$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -627,17 +603,15 @@ class VideoPlayerInstanceApi { } Future dispose() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.dispose$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -652,17 +626,15 @@ class VideoPlayerInstanceApi { } Future> getAudioTracks() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getAudioTracks$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -683,19 +655,52 @@ class VideoPlayerInstanceApi { } Future selectAudioTrack(int trackIndex) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.selectAudioTrack$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [trackIndex], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; + final pigeonVar_replyList = await pigeonVar_sendFuture 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; + } + } + + /// Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. + /// Pass 0 to remove any bandwidth limit and allow the player to select quality freely. + /// Common values: + /// - 360p: 500000 bps (500 kbps) + /// - 480p: 800000 bps (800 kbps) + /// - 720p: 1200000 bps (1.2 Mbps) + /// - 1080p: 2500000 bps (2.5 Mbps) + /// + /// Note: On iOS/macOS, this sets the preferredPeakBitRate on AVPlayerItem, + /// which influences AVPlayer's HLS variant selection. + Future setBandwidthLimit(int maxBandwidthBps) async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setBandwidthLimit$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [maxBandwidthBps], + ); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { diff --git a/packages/video_player/video_player_avfoundation/pigeons/messages.dart b/packages/video_player/video_player_avfoundation/pigeons/messages.dart index f49b46005307..bfb81c9ee62e 100644 --- a/packages/video_player/video_player_avfoundation/pigeons/messages.dart +++ b/packages/video_player/video_player_avfoundation/pigeons/messages.dart @@ -93,4 +93,17 @@ abstract class VideoPlayerInstanceApi { List getAudioTracks(); @ObjCSelector('selectAudioTrackAtIndex:') void selectAudioTrack(int trackIndex); + + /// Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. + /// Pass 0 to remove any bandwidth limit and allow the player to select quality freely. + /// Common values: + /// - 360p: 500000 bps (500 kbps) + /// - 480p: 800000 bps (800 kbps) + /// - 720p: 1200000 bps (1.2 Mbps) + /// - 1080p: 2500000 bps (2.5 Mbps) + /// + /// Note: On iOS/macOS, this sets the preferredPeakBitRate on AVPlayerItem, + /// which influences AVPlayer's HLS variant selection. + @ObjCSelector('setBandwidthLimit:') + void setBandwidthLimit(int maxBandwidthBps); } diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index c932317b1f3f..a72d527301d1 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.9.0 +version: 2.9.1 environment: sdk: ^3.10.0 diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index a0e403be5dc2..c13bf6076999 100644 --- a/packages/video_player/video_player_platform_interface/CHANGELOG.md +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -1,5 +1,6 @@ ## NEXT +* Automatic Bitrate Streaming support to Android and iOS. * Updates minimum supported SDK version to Flutter 3.32/Dart 3.8. ## 6.6.0 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 1cec5f42c218..1b0a1134471f 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 @@ -153,6 +153,35 @@ abstract class VideoPlayerPlatform extends PlatformInterface { bool isAudioTrackSupportAvailable() { return false; } + + /// Sets the maximum bandwidth limit in bits per second for HLS adaptive bitrate streaming. + /// + /// This method helps control which HLS variant streams are selected by the player + /// by limiting the available bandwidth. This is useful for: + /// - Forcing lower quality playback on slow networks + /// - Preventing excessive buffering when network is unstable + /// - Testing quality switching behavior + /// + /// Pass 0 to remove any bandwidth limit and allow the player to select quality freely. + /// + /// Common bandwidth values for HLS: + /// - 360p: 500000 bps (500 kbps) + /// - 480p: 800000 bps (800 kbps) + /// - 720p: 1200000 bps (1.2 Mbps) + /// - 1080p: 2500000 bps (2.5 Mbps) + /// + /// Platform-specific behavior: + /// - **Android**: Uses ExoPlayer's DefaultBandwidthMeter. The bandwidth meter estimates + /// network speed from segment downloads. Setting a limit influences track selection. + /// - **iOS/macOS**: Sets AVPlayerItem.preferredPeakBitRate, which AVPlayer uses + /// to select HLS variant streams. + /// - **Web**: Not implemented (returns unimplemented error). + /// + /// Returns: A Future that completes when the bandwidth limit has been applied. + /// Throws: [UnimplementedError] on platforms that don't support this feature. + Future setBandwidthLimit(int playerId, int maxBandwidthBps) { + throw UnimplementedError('setBandwidthLimit() has not been implemented.'); + } } class _PlaceholderImplementation extends VideoPlayerPlatform {} diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml index 2d93a65e8ad7..5ed8306031ca 100644 --- a/packages/video_player/video_player_platform_interface/pubspec.yaml +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -4,6 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/video_player/ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes + version: 6.6.0 environment: