Skip to content
Open
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
7 changes: 7 additions & 0 deletions packages/video_player/video_player/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ dev_dependencies:
path_provider: ^2.0.6
test: any

# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
video_player_android: {path: ../../../../packages/video_player/video_player_android}
video_player_avfoundation: {path: ../../../../packages/video_player/video_player_avfoundation}
video_player_platform_interface: {path: ../../../../packages/video_player/video_player_platform_interface}

flutter:
uses-material-design: true
assets:
Expand Down
6 changes: 6 additions & 0 deletions packages/video_player/video_player/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ dev_dependencies:
topics:
- video
- video-player
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
video_player_android: {path: ../../../packages/video_player/video_player_android}
video_player_avfoundation: {path: ../../../packages/video_player/video_player_avfoundation}
video_player_platform_interface: {path: ../../../packages/video_player/video_player_platform_interface}
30 changes: 23 additions & 7 deletions packages/video_player/video_player/test/video_player_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1030,13 +1030,11 @@ void main() {
}

expect(isSorted, false, reason: 'Expected captions to be unsorted');
expect(captions.map((Caption c) => c.text).toList(), <String>[
'one',
'two',
'three',
'five',
'four',
], reason: 'Captions should be in original unsorted order');
expect(
captions.map((Caption c) => c.text).toList(),
<String>['one', 'two', 'three', 'five', 'four'],
reason: 'Captions should be in original unsorted order',
);
});

test('works when seeking, includes all captions', () async {
Expand Down Expand Up @@ -1774,6 +1772,22 @@ void main() {
);
});

test('preferredAudioLanguage is forwarded to createWithOptions', () async {
final controller = VideoPlayerController.networkUrl(
_localhostUri,
videoPlayerOptions: VideoPlayerOptions(preferredAudioLanguage: 'es'),
);
addTearDown(controller.dispose);

await controller.initialize();

expect(fakeVideoPlayerPlatform.creationOptions.length, 1);
expect(
fakeVideoPlayerPlatform.creationOptions[0].videoPlayerOptions?.preferredAudioLanguage,
'es',
);
});

test('true allowBackgroundPlayback continues playback', () async {
final controller = VideoPlayerController.networkUrl(
_localhostUri,
Expand Down Expand Up @@ -1919,6 +1933,7 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform {
List<String> calls = <String>[];
List<DataSource> dataSources = <DataSource>[];
List<VideoViewType> viewTypes = <VideoViewType>[];
List<VideoCreationOptions> creationOptions = <VideoCreationOptions>[];
final Map<int, StreamController<VideoEvent>> streams = <int, StreamController<VideoEvent>>{};
List<VideoPlayerOptions?> videoPlayerOptions = <VideoPlayerOptions?>[];
bool forceInitError = false;
Expand Down Expand Up @@ -1965,6 +1980,7 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform {
dataSources.add(options.dataSource);
viewTypes.add(options.viewType);
videoPlayerOptions.add(options.videoPlayerOptions);
creationOptions.add(options);
return nextPlayerId++;
}

Expand Down
4 changes: 4 additions & 0 deletions packages/video_player/video_player_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.12.0

* Adds support for setting a preferred audio language during player creation via `VideoPlayerOptions.preferredAudioLanguage`.

## 2.11.0

* Adds `backBufferDurationMs` to `CreationOptions` to configure ExoPlayer `DefaultLoadControl` back buffer duration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public VideoPlayer(
exoPlayer.prepare();
exoPlayer.addListener(createExoPlayerEventListener(exoPlayer, surfaceProducer));
setAudioAttributes(exoPlayer, options.mixWithOthers);
setPreferredAudioLanguage(exoPlayer, options.preferredAudioLanguage);
}

public void setDisposeHandler(@Nullable DisposeHandler handler) {
Expand Down Expand Up @@ -447,6 +448,18 @@ public void selectVideoTrack(long groupIndex, long trackIndex) {
trackSelector.buildUponParameters().setOverrideForType(override).build());
}

private static void setPreferredAudioLanguage(
ExoPlayer exoPlayer, String preferredAudioLanguage) {
if (preferredAudioLanguage != null) {
exoPlayer.setTrackSelectionParameters(
exoPlayer
.getTrackSelectionParameters()
.buildUpon()
.setPreferredAudioLanguage(preferredAudioLanguage)
.build());
}
}

public void dispose() {
isDisposed = true;
mainHandler.removeCallbacksAndMessages(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

public class VideoPlayerOptions {
public boolean mixWithOthers;
@Nullable public String preferredAudioLanguage;

/**
* The duration of the back buffer in milliseconds, used to configure ExoPlayer's load control.
Expand All @@ -21,5 +22,6 @@ public VideoPlayerOptions() {}
public VideoPlayerOptions(@NonNull VideoPlayerOptions other) {
this.mixWithOthers = other.mixWithOthers;
this.backBufferDurationMs = other.backBufferDurationMs;
this.preferredAudioLanguage = other.preferredAudioLanguage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ flutter:
assets:
- assets/flutter-mark-square-64.png
- assets/Butterfly-209.mp4
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
video_player_platform_interface: {path: ../../../../packages/video_player/video_player_platform_interface}
6 changes: 5 additions & 1 deletion packages/video_player/video_player_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: video_player_android
description: Android implementation of the video_player plugin.
repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
version: 2.11.0
version: 2.12.0

environment:
sdk: ^3.12.0
Expand Down Expand Up @@ -33,3 +33,7 @@ dev_dependencies:
topics:
- video
- video-player
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
video_player_platform_interface: {path: ../../../packages/video_player/video_player_platform_interface}
4 changes: 4 additions & 0 deletions packages/video_player/video_player_avfoundation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.11.1

* Adds support for setting a preferred audio language during player creation via `VideoPlayerOptions.preferredAudioLanguage`.

## 2.11.0

* Implements `setPreventsDisplaySleepDuringVideoPlayback` using
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public final class VideoPlayerPlugin: NSObject, FlutterPlugin, AVFoundationVideo
func createPlatformViewPlayer(options params: CreationOptions) throws -> Int64 {
let item = try playerItem(with: params)
let player = FVPVideoPlayer(playerItem: item, avFactory: avFactory, viewProvider: viewProvider)
player.preferredAudioLanguage = params.preferredAudioLanguage
return configurePlayer(player, extraDisposeHandler: nil)
}

Expand All @@ -183,6 +184,7 @@ public final class VideoPlayerPlugin: NSObject, FlutterPlugin, AVFoundationVideo
avFactory: avFactory,
viewProvider: viewProvider
)
player.preferredAudioLanguage = creationOptions.preferredAudioLanguage

let textureId = textureRegistry.register(player)
player.setTextureIdentifier(textureId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private func doubleEqualsVideoPlayerPluginMessages(_ lhs: Double, _ rhs: Double)

private func doubleHashVideoPlayerPluginMessages(_ value: Double, _ hasher: inout Hasher) {
if value.isNaN {
hasher.combine(0x7FF8_0000_0000_0000)
hasher.combine(0x7FF8000000000000)
} else {
// Normalize -0.0 to 0.0
hasher.combine(value == 0 ? 0 : value)
Expand Down Expand Up @@ -176,12 +176,14 @@ func deepHashVideoPlayerPluginMessages(value: Any?, hasher: inout Hasher) {
}
}


/// Information passed to the platform view creation.
///
/// Generated class from Pigeon that represents data sent in messages.
struct PlatformVideoViewCreationParams: Hashable {
var playerId: Int64


// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> PlatformVideoViewCreationParams? {
let playerId = pigeonVar_list[0] as! Int64
Expand All @@ -195,9 +197,7 @@ struct PlatformVideoViewCreationParams: Hashable {
playerId
]
}
static func == (lhs: PlatformVideoViewCreationParams, rhs: PlatformVideoViewCreationParams)
-> Bool
{
static func == (lhs: PlatformVideoViewCreationParams, rhs: PlatformVideoViewCreationParams) -> Bool {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
Expand All @@ -214,35 +214,40 @@ struct PlatformVideoViewCreationParams: Hashable {
struct CreationOptions: Hashable {
var uri: String
var httpHeaders: [String: String]
var preferredAudioLanguage: String? = nil


// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> CreationOptions? {
let uri = pigeonVar_list[0] as! String
let httpHeaders = pigeonVar_list[1] as! [String: String]
let preferredAudioLanguage: String? = nilOrValue(pigeonVar_list[2])

return CreationOptions(
uri: uri,
httpHeaders: httpHeaders
httpHeaders: httpHeaders,
preferredAudioLanguage: preferredAudioLanguage
)
}
func toList() -> [Any?] {
return [
uri,
httpHeaders,
preferredAudioLanguage,
]
}
static func == (lhs: CreationOptions, rhs: CreationOptions) -> Bool {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsVideoPlayerPluginMessages(lhs.uri, rhs.uri)
&& deepEqualsVideoPlayerPluginMessages(lhs.httpHeaders, rhs.httpHeaders)
return deepEqualsVideoPlayerPluginMessages(lhs.uri, rhs.uri) && deepEqualsVideoPlayerPluginMessages(lhs.httpHeaders, rhs.httpHeaders) && deepEqualsVideoPlayerPluginMessages(lhs.preferredAudioLanguage, rhs.preferredAudioLanguage)
}

func hash(into hasher: inout Hasher) {
hasher.combine("CreationOptions")
deepHashVideoPlayerPluginMessages(value: uri, hasher: &hasher)
deepHashVideoPlayerPluginMessages(value: httpHeaders, hasher: &hasher)
deepHashVideoPlayerPluginMessages(value: preferredAudioLanguage, hasher: &hasher)
}
}

Expand All @@ -251,6 +256,7 @@ struct TexturePlayerIds: Hashable {
var playerId: Int64
var textureId: Int64


// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> TexturePlayerIds? {
let playerId = pigeonVar_list[0] as! Int64
Expand All @@ -271,8 +277,7 @@ struct TexturePlayerIds: Hashable {
if Swift.type(of: lhs) != Swift.type(of: rhs) {
return false
}
return deepEqualsVideoPlayerPluginMessages(lhs.playerId, rhs.playerId)
&& deepEqualsVideoPlayerPluginMessages(lhs.textureId, rhs.textureId)
return deepEqualsVideoPlayerPluginMessages(lhs.playerId, rhs.playerId) && deepEqualsVideoPlayerPluginMessages(lhs.textureId, rhs.textureId)
}

func hash(into hasher: inout Hasher) {
Expand Down Expand Up @@ -325,8 +330,7 @@ private class VideoPlayerPluginMessagesPigeonCodecReaderWriter: FlutterStandardR
}

class VideoPlayerPluginMessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = VideoPlayerPluginMessagesPigeonCodec(
readerWriter: VideoPlayerPluginMessagesPigeonCodecReaderWriter())
static let shared = VideoPlayerPluginMessagesPigeonCodec(readerWriter: VideoPlayerPluginMessagesPigeonCodecReaderWriter())
}

/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
Expand All @@ -342,15 +346,9 @@ protocol AVFoundationVideoPlayerApi {
class AVFoundationVideoPlayerApiSetup {
static var codec: FlutterStandardMessageCodec { VideoPlayerPluginMessagesPigeonCodec.shared }
/// Sets up an instance of `AVFoundationVideoPlayerApi` to handle messages through the `binaryMessenger`.
static func setUp(
binaryMessenger: FlutterBinaryMessenger, api: AVFoundationVideoPlayerApi?,
messageChannelSuffix: String = ""
) {
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: AVFoundationVideoPlayerApi?, messageChannelSuffix: String = "") {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
let initializeChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.initialize\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
let initializeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.initialize\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
initializeChannel.setMessageHandler { _, reply in
do {
Expand All @@ -363,10 +361,7 @@ class AVFoundationVideoPlayerApiSetup {
} else {
initializeChannel.setMessageHandler(nil)
}
let createForPlatformViewChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForPlatformView\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
let createForPlatformViewChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForPlatformView\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
createForPlatformViewChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
Expand All @@ -381,10 +376,7 @@ class AVFoundationVideoPlayerApiSetup {
} else {
createForPlatformViewChannel.setMessageHandler(nil)
}
let createForTextureViewChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForTextureView\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
let createForTextureViewChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForTextureView\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
createForTextureViewChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
Expand All @@ -399,10 +391,7 @@ class AVFoundationVideoPlayerApiSetup {
} else {
createForTextureViewChannel.setMessageHandler(nil)
}
let setMixWithOthersChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
let setMixWithOthersChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
setMixWithOthersChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
Expand All @@ -417,10 +406,7 @@ class AVFoundationVideoPlayerApiSetup {
} else {
setMixWithOthersChannel.setMessageHandler(nil)
}
let getAssetUrlChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getAssetUrl\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
let getAssetUrlChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getAssetUrl\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getAssetUrlChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,10 +407,41 @@ - (void)reportInitialized {
NSAssert(!_isInitialized, @"reportInitializedIfReadyToPlay should only be called once.");

_isInitialized = YES;
[self setPreferredAudioLanguage];
[self.eventListener videoPlayerDidInitializeWithDuration:self.duration
size:currentItem.presentationSize];
}

/// Selects the audio track whose locale language code matches self.preferredAudioLanguage.
/// Does nothing if preferredAudioLanguage is nil, the player is not yet initialized, or no
/// matching track is found.
- (void)setPreferredAudioLanguage {
if (!self.preferredAudioLanguage || !_isInitialized) {
return;
}

NSString *preferredLanguage = self.preferredAudioLanguage;

AVPlayerItem *currentItem = _player.currentItem;
if (!currentItem) {
return;
}

AVMediaSelectionGroup *audioGroup =
[currentItem.asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
if (!audioGroup) {
return;
}

for (AVMediaSelectionOption *option in audioGroup.options) {
NSString *optionLanguageCode = option.locale.languageCode;
if (optionLanguageCode && [optionLanguageCode isEqualToString:preferredLanguage]) {
[currentItem selectMediaOption:option inMediaSelectionGroup:audioGroup];
break;
}
}
}

#pragma mark - FVPVideoPlayerInstanceApi

- (void)playWithError:(FlutterError *_Nullable *_Nonnull)error {
Expand Down
Loading