Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions packages/video_player/video_player/lib/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class VideoPlayerValue {
this.isPlaying = false,
this.isLooping = false,
this.isBuffering = false,
this.isPipActive = false,
this.isAutoPipEnabled = false,
this.volume = 1.0,
this.playbackSpeed = 1.0,
this.rotationCorrection = 0,
Expand Down Expand Up @@ -99,6 +101,12 @@ class VideoPlayerValue {
/// True if the video is currently buffering.
final bool isBuffering;

/// True if the video is currently in Picture-in-Picture mode.
final bool isPipActive;

/// True if automatic Picture-in-Picture is enabled.
final bool isAutoPipEnabled;

/// The current volume of the playback.
final double volume;

Expand Down Expand Up @@ -153,6 +161,8 @@ class VideoPlayerValue {
bool? isPlaying,
bool? isLooping,
bool? isBuffering,
bool? isPipActive,
bool? isAutoPipEnabled,
double? volume,
double? playbackSpeed,
int? rotationCorrection,
Expand All @@ -169,6 +179,8 @@ class VideoPlayerValue {
isPlaying: isPlaying ?? this.isPlaying,
isLooping: isLooping ?? this.isLooping,
isBuffering: isBuffering ?? this.isBuffering,
isPipActive: isPipActive ?? this.isPipActive,
isAutoPipEnabled: isAutoPipEnabled ?? this.isAutoPipEnabled,
volume: volume ?? this.volume,
playbackSpeed: playbackSpeed ?? this.playbackSpeed,
rotationCorrection: rotationCorrection ?? this.rotationCorrection,
Expand All @@ -191,6 +203,8 @@ class VideoPlayerValue {
'isPlaying: $isPlaying, '
'isLooping: $isLooping, '
'isBuffering: $isBuffering, '
'isPipActive: $isPipActive, '
'isAutoPipEnabled: $isAutoPipEnabled, '
'volume: $volume, '
'playbackSpeed: $playbackSpeed, '
'errorDescription: $errorDescription)';
Expand Down Expand Up @@ -692,6 +706,41 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
await _applyMaxVideoResolution();
}

/// Requests Picture-in-Picture mode.
///
/// This API is currently a no-op.
Future<void> startPictureInPicture() async {}

/// Requests leaving Picture-in-Picture mode.
///
/// This API is currently a no-op.
Future<void> stopPictureInPicture() async {}

/// Returns whether Picture-in-Picture is supported on this device.
///
/// This API currently always returns false.
Future<bool> isPictureInPictureSupported() async => false;

/// Returns whether Picture-in-Picture is currently active.
///
/// This API currently always returns false.
Future<bool> isPictureInPictureActive() async => false;

/// Enables or disables automatic Picture-in-Picture.
///
/// This API is currently a no-op.
Future<void> setAutoPictureInPicture(bool enabled) async {}

/// Sets whether Picture-in-Picture requires linear playback at runtime.
///
/// This API is currently a no-op.
Future<void> setRequiresLinearPlayback(bool requiresLinearPlayback) async {}

/// Callback that returns the screen rect of the video Texture widget.
///
/// This API is currently unused.
Rect? Function()? pipSourceRectProvider;

/// Sets the caption offset.
///
/// The [offset] will be used when getting the correct caption for a specific position.
Expand Down
58 changes: 54 additions & 4 deletions packages/video_player/video_player/test/video_player_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ class FakeController extends ValueNotifier<VideoPlayerValue>
@override
Future<void> setMaxVideoResolution(int? width, int? height) async {}

@override
Future<void> startPictureInPicture() async {}

@override
Future<void> stopPictureInPicture() async {}

@override
Future<bool> isPictureInPictureSupported() async => false;

@override
Future<bool> isPictureInPictureActive() async => false;

@override
Future<void> setAutoPictureInPicture(bool enabled) async {}

@override
Future<void> setRequiresLinearPlayback(bool requiresLinearPlayback) async {}

@override
Rect? Function()? pipSourceRectProvider;

@override
Future<void> initialize() async {}

Expand Down Expand Up @@ -87,10 +108,7 @@ class FakeController extends ValueNotifier<VideoPlayerValue>
) async {}

@override
Future<bool?> get isPlaying async => value.isPlaying;

@override
Future<void> setBuffer(Buffer buffer) async {}
Future<bool> get isPlaying async => value.isPlaying;
}

Future<ClosedCaptionFile> _loadClosedCaption() async =>
Expand Down Expand Up @@ -857,6 +875,8 @@ void main() {
expect(uninitialized.isPlaying, isFalse);
expect(uninitialized.isLooping, isFalse);
expect(uninitialized.isBuffering, isFalse);
expect(uninitialized.isPipActive, isFalse);
expect(uninitialized.isAutoPipEnabled, isFalse);
expect(uninitialized.volume, 1.0);
expect(uninitialized.playbackSpeed, 1.0);
expect(uninitialized.errorDescription, isNull);
Expand All @@ -878,6 +898,8 @@ void main() {
expect(error.isPlaying, isFalse);
expect(error.isLooping, isFalse);
expect(error.isBuffering, isFalse);
expect(error.isPipActive, isFalse);
expect(error.isAutoPipEnabled, isFalse);
expect(error.volume, 1.0);
expect(error.playbackSpeed, 1.0);
expect(error.errorDescription, errorMessage);
Expand Down Expand Up @@ -931,6 +953,8 @@ void main() {
'isPlaying: true, '
'isLooping: true, '
'isBuffering: true, '
'isPipActive: false, '
'isAutoPipEnabled: false, '
'volume: 0.5, '
'playbackSpeed: 1.5, '
'errorDescription: null)');
Expand Down Expand Up @@ -1085,6 +1109,32 @@ void main() {
});
});

group('pictureInPicture', () {
late FakeVideoPlayerPlatform fakeVideoPlayerPlatform;

setUp(() {
fakeVideoPlayerPlatform = FakeVideoPlayerPlatform();
VideoPlayerPlatform.instance = fakeVideoPlayerPlatform;
});

test('public API is usable as no-op', () async {
final VideoPlayerController controller =
VideoPlayerController.file(File(''));
await controller.initialize();
final int callCount = fakeVideoPlayerPlatform.calls.length;

await controller.startPictureInPicture();
await controller.stopPictureInPicture();
await controller.setAutoPictureInPicture(true);
await controller.setRequiresLinearPlayback(true);

expect(await controller.isPictureInPictureSupported(), isFalse);
expect(await controller.isPictureInPictureActive(), isFalse);
expect(fakeVideoPlayerPlatform.calls.length, callCount);
await controller.dispose();
});
});

test('VideoProgressColors', () {
const Color playedColor = Color.fromRGBO(0, 0, 255, 0.75);
const Color bufferedColor = Color.fromRGBO(0, 255, 0, 0.5);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ abstract class VideoPlayerPlatform extends PlatformInterface {
Future<bool> getIsPlaying(int textureId) {
throw UnimplementedError('isPlaying() has not been implemented.');
}

/// Sets whether Picture-in-Picture requires linear playback.
///
/// When `true`, the PiP window hides the skip forward/backward buttons.
/// iOS 14.2+ only; silently ignored on other platforms.
Future<void> setRequiresLinearPlayback(
int textureId,
bool requiresLinearPlayback,
) async {}
}

/// バッファを調整するための各パラメーター
Expand Down Expand Up @@ -430,6 +439,7 @@ class VideoPlayerOptions {
this.mixWithOthers = false,
this.allowBackgroundPlayback = false,
this.buffer,
this.requiresLinearPlayback = false,
});

/// Set this to true to keep playing video in background, when app goes in background.
Expand All @@ -446,4 +456,11 @@ class VideoPlayerOptions {
/// AndroidとiOSでバッファの値を調整するためにセットします
/// nullの場合は各プラットフォームのPlayerのデフォルトの値が使われます
final Buffer? buffer;

/// Set this to true to hide the skip forward/backward buttons in the
/// Picture-in-Picture window. The default value is false.
///
/// Note: This option is only supported on iOS 14.2+ and is silently
/// ignored on other platforms.
final bool requiresLinearPlayback;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,11 @@ void main() {
expect(options.mixWithOthers, false);
},
);
test(
'VideoPlayerOptions requiresLinearPlayback defaults to false',
() {
final VideoPlayerOptions options = VideoPlayerOptions();
expect(options.requiresLinearPlayback, false);
},
);
}