Skip to content

Commit f919678

Browse files
authored
Merge branch 'main' into fix/rfw-font-weight-release-mode
2 parents 1944db2 + cac0fc5 commit f919678

8 files changed

Lines changed: 266 additions & 5 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,7 @@ gradlew.bat
6262
.project
6363
.classpath
6464
.settings
65+
66+
# AI related
67+
.agents/rules/personal-*
68+
.agents/skills/personal-*

packages/file_selector/file_selector_windows/example/lib/get_directory_page.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ class GetDirectoryPage extends StatelessWidget {
1414
Future<void> _getDirectoryPath(BuildContext context) async {
1515
const confirmButtonText = 'Choose';
1616
final String? directoryPath = await FileSelectorPlatform.instance
17-
.getDirectoryPath(confirmButtonText: confirmButtonText);
17+
.getDirectoryPathWithOptions(
18+
const FileDialogOptions(confirmButtonText: confirmButtonText),
19+
);
1820
if (directoryPath == null) {
1921
// Operation was canceled by the user.
2022
return;

packages/file_selector/file_selector_windows/example/lib/get_multiple_directories_page.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ class GetMultipleDirectoriesPage extends StatelessWidget {
1414
Future<void> _getDirectoryPaths(BuildContext context) async {
1515
const confirmButtonText = 'Choose';
1616
final List<String?> directoriesPaths = await FileSelectorPlatform.instance
17-
.getDirectoryPaths(confirmButtonText: confirmButtonText);
17+
.getDirectoryPathsWithOptions(
18+
const FileDialogOptions(confirmButtonText: confirmButtonText),
19+
);
1820
if (directoriesPaths.isEmpty) {
1921
// Operation was canceled by the user.
2022
return;

packages/file_selector/file_selector_windows/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ environment:
88
flutter: ">=3.35.0"
99

1010
dependencies:
11-
file_selector_platform_interface: ^2.6.0
11+
file_selector_platform_interface: ^2.7.0
1212
file_selector_windows:
1313
# When depending on this package from a real application you should use:
1414
# file_selector_windows: ^x.y.z

packages/video_player/video_player_platform_interface/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 6.7.0
22

3+
* Adds `VideoTrack` class and `getVideoTracks()`, `selectVideoTrack()`, `isVideoTrackSupportAvailable()` methods for video track (quality) selection.
34
* Updates minimum supported SDK version to Flutter 3.35/Dart 3.9.
45

56
## 6.6.0

packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,41 @@ abstract class VideoPlayerPlatform extends PlatformInterface {
153153
bool isAudioTrackSupportAvailable() {
154154
return false;
155155
}
156+
157+
/// Gets the available video tracks (quality variants) for the video.
158+
///
159+
/// Returns a list of [VideoTrack] objects representing the available
160+
/// video quality variants. For HLS/DASH streams, this returns the different
161+
/// quality levels available. For non-adaptive videos, platform
162+
/// implementations may return one or more tracks, or an empty list,
163+
/// depending on the media and the metadata available.
164+
Future<List<VideoTrack>> getVideoTracks(int playerId) {
165+
throw UnimplementedError('getVideoTracks() has not been implemented.');
166+
}
167+
168+
/// Selects which video track (quality variant) is chosen for playback.
169+
///
170+
/// Pass a [VideoTrack] to select a specific quality.
171+
/// Pass `null` to enable automatic quality selection (adaptive streaming).
172+
Future<void> selectVideoTrack(int playerId, VideoTrack? track) {
173+
throw UnimplementedError('selectVideoTrack() has not been implemented.');
174+
}
175+
176+
/// Returns whether video track selection is supported on this platform.
177+
///
178+
/// This method allows developers to query at runtime whether the current
179+
/// platform supports video track (quality) selection functionality. This is
180+
/// useful for platforms like web where video track selection may not be
181+
/// available.
182+
///
183+
/// Returns `true` if [getVideoTracks] and [selectVideoTrack] are supported,
184+
/// `false` otherwise.
185+
///
186+
/// The default implementation returns `false`. Platform implementations
187+
/// should override this to return `true` if they support video track selection.
188+
bool isVideoTrackSupportAvailable() {
189+
return false;
190+
}
156191
}
157192

158193
class _PlaceholderImplementation extends VideoPlayerPlatform {}
@@ -652,3 +687,102 @@ class VideoAudioTrack {
652687
'channelCount: $channelCount, '
653688
'codec: $codec)';
654689
}
690+
691+
/// Represents a video track (quality variant) in a video with its metadata.
692+
///
693+
/// For HLS/DASH streams, each [VideoTrack] represents a different quality
694+
/// level (e.g., 1080p, 720p, 480p). For regular videos, there may be only
695+
/// one track or none available.
696+
@immutable
697+
class VideoTrack {
698+
/// Constructs an instance of [VideoTrack].
699+
const VideoTrack({
700+
required this.id,
701+
required this.isSelected,
702+
this.label,
703+
this.bitrate,
704+
this.width,
705+
this.height,
706+
this.frameRate,
707+
this.codec,
708+
});
709+
710+
/// Unique identifier for the video track.
711+
///
712+
/// The format is platform-specific:
713+
/// - Android: `"{groupIndex}_{trackIndex}"` (e.g., `"0_2"`)
714+
/// - iOS: `"variant_{bitrate}"` for HLS, `"asset_{trackID}"` for regular videos
715+
final String id;
716+
717+
/// Whether this track is currently selected.
718+
final bool isSelected;
719+
720+
/// Human-readable label for the track (e.g., "1080p", "720p").
721+
///
722+
/// May be null if not available from the platform.
723+
final String? label;
724+
725+
/// Bitrate of the video track in bits per second.
726+
///
727+
/// May be null if not available from the platform.
728+
final int? bitrate;
729+
730+
/// Video width in pixels.
731+
///
732+
/// May be null if not available from the platform.
733+
final int? width;
734+
735+
/// Video height in pixels.
736+
///
737+
/// May be null if not available from the platform.
738+
final int? height;
739+
740+
/// Frame rate in frames per second.
741+
///
742+
/// May be null if not available from the platform.
743+
final double? frameRate;
744+
745+
/// Video codec used (e.g., "avc1", "hevc", "vp9").
746+
///
747+
/// May be null if not available from the platform.
748+
final String? codec;
749+
750+
@override
751+
bool operator ==(Object other) {
752+
return identical(this, other) ||
753+
other is VideoTrack &&
754+
runtimeType == other.runtimeType &&
755+
id == other.id &&
756+
isSelected == other.isSelected &&
757+
label == other.label &&
758+
bitrate == other.bitrate &&
759+
width == other.width &&
760+
height == other.height &&
761+
frameRate == other.frameRate &&
762+
codec == other.codec;
763+
}
764+
765+
@override
766+
int get hashCode => Object.hash(
767+
id,
768+
isSelected,
769+
label,
770+
bitrate,
771+
width,
772+
height,
773+
frameRate,
774+
codec,
775+
);
776+
777+
@override
778+
String toString() =>
779+
'VideoTrack('
780+
'id: $id, '
781+
'isSelected: $isSelected, '
782+
'label: $label, '
783+
'bitrate: $bitrate, '
784+
'width: $width, '
785+
'height: $height, '
786+
'frameRate: $frameRate, '
787+
'codec: $codec)';
788+
}

packages/video_player/video_player_platform_interface/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/video_player/
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
55
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
66
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
7-
version: 6.6.0
7+
version: 6.7.0
88

99
environment:
1010
sdk: ^3.9.0

packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,122 @@ void main() {
4040
test('default implementation isAudioTrackSupportAvailable returns false', () {
4141
expect(initialInstance.isAudioTrackSupportAvailable(), false);
4242
});
43+
44+
test('default implementation getVideoTracks throws unimplemented', () async {
45+
await expectLater(
46+
() => initialInstance.getVideoTracks(1),
47+
throwsUnimplementedError,
48+
);
49+
});
50+
51+
test(
52+
'default implementation selectVideoTrack throws unimplemented',
53+
() async {
54+
await expectLater(
55+
() => initialInstance.selectVideoTrack(
56+
1,
57+
const VideoTrack(id: 'test', isSelected: false),
58+
),
59+
throwsUnimplementedError,
60+
);
61+
},
62+
);
63+
64+
test('default implementation isVideoTrackSupportAvailable returns false', () {
65+
expect(initialInstance.isVideoTrackSupportAvailable(), false);
66+
});
67+
68+
group('VideoTrack', () {
69+
test('constructor creates instance with required fields', () {
70+
const track = VideoTrack(id: 'track_1', isSelected: true);
71+
expect(track.id, 'track_1');
72+
expect(track.isSelected, true);
73+
expect(track.label, isNull);
74+
expect(track.bitrate, isNull);
75+
expect(track.width, isNull);
76+
expect(track.height, isNull);
77+
expect(track.frameRate, isNull);
78+
expect(track.codec, isNull);
79+
});
80+
81+
test('constructor creates instance with all fields', () {
82+
const track = VideoTrack(
83+
id: 'track_1',
84+
isSelected: true,
85+
label: '1080p',
86+
bitrate: 5000000,
87+
width: 1920,
88+
height: 1080,
89+
frameRate: 30.0,
90+
codec: 'avc1',
91+
);
92+
expect(track.id, 'track_1');
93+
expect(track.isSelected, true);
94+
expect(track.label, '1080p');
95+
expect(track.bitrate, 5000000);
96+
expect(track.width, 1920);
97+
expect(track.height, 1080);
98+
expect(track.frameRate, 30.0);
99+
expect(track.codec, 'avc1');
100+
});
101+
102+
test('equality works correctly', () {
103+
const track1 = VideoTrack(
104+
id: 'track_1',
105+
isSelected: true,
106+
label: '1080p',
107+
bitrate: 5000000,
108+
);
109+
const track2 = VideoTrack(
110+
id: 'track_1',
111+
isSelected: true,
112+
label: '1080p',
113+
bitrate: 5000000,
114+
);
115+
const track3 = VideoTrack(id: 'track_2', isSelected: false);
116+
117+
expect(track1, equals(track2));
118+
expect(track1, isNot(equals(track3)));
119+
});
120+
121+
test('hashCode is consistent with equality', () {
122+
const track1 = VideoTrack(
123+
id: 'track_1',
124+
isSelected: true,
125+
label: '1080p',
126+
);
127+
const track2 = VideoTrack(
128+
id: 'track_1',
129+
isSelected: true,
130+
label: '1080p',
131+
);
132+
133+
expect(track1.hashCode, equals(track2.hashCode));
134+
});
135+
136+
test('toString returns expected format', () {
137+
const track = VideoTrack(
138+
id: 'track_1',
139+
isSelected: true,
140+
label: '1080p',
141+
bitrate: 5000000,
142+
width: 1920,
143+
height: 1080,
144+
frameRate: 30.0,
145+
codec: 'avc1',
146+
);
147+
148+
final str = track.toString();
149+
expect(str, contains('VideoTrack'));
150+
expect(str, contains('id: track_1'));
151+
expect(str, contains('isSelected: true'));
152+
expect(str, contains('label: 1080p'));
153+
expect(str, contains('bitrate: 5000000'));
154+
expect(str, contains('width: 1920'));
155+
expect(str, contains('height: 1080'));
156+
// Accept both '30' and '30.0' (web JS omits trailing .0 for whole-number doubles)
157+
expect(str, contains('frameRate: 30'));
158+
expect(str, contains('codec: avc1'));
159+
});
160+
});
43161
}

0 commit comments

Comments
 (0)