From d8810095ac94e25e8a53bd6f8217a475f883a1e5 Mon Sep 17 00:00:00 2001 From: Elena Nazarova Date: Thu, 9 Nov 2023 13:40:20 +0300 Subject: [PATCH] =?UTF-8?q?PSDK-1029=20-=20=D0=97=D0=B0=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D0=B6=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/settings_repository.dart | 6 +- .../lib/src/common/views/input_view.dart | 116 ++++++++++++++---- .../domain/bloc/playerview_bloc.dart | 9 +- .../settings_screen/domain/settings_bloc.dart | 38 +++++- .../domain/settings_event.dart | 18 +++ .../domain/settings_state.dart | 33 +++-- .../presentation/settings_view.dart | 7 +- nut_player/example/pubspec.yaml | 1 + .../controller/video_player_controller.dart | 2 +- .../lib/src/nut_player_android.dart | 2 +- .../ios/Classes/NutPlayerIosPlugin.swift | 2 +- .../ios/Classes/NutPlayerViewFactory.swift | 6 +- .../ios/Classes/PlayerFlutterPlatform.swift | 23 +++- nut_player_ios/lib/src/nut_player_ios.dart | 2 +- .../lib/src/nut_player_platform.dart | 2 +- 15 files changed, 213 insertions(+), 54 deletions(-) diff --git a/nut_player/example/lib/src/common/repository/settings_repository.dart b/nut_player/example/lib/src/common/repository/settings_repository.dart index 6055920..c8daccc 100644 --- a/nut_player/example/lib/src/common/repository/settings_repository.dart +++ b/nut_player/example/lib/src/common/repository/settings_repository.dart @@ -23,7 +23,7 @@ class SettingsRepository { late bool isLoop; - late int manifest; + late int playlist; late int track; late int chunk; @@ -44,7 +44,7 @@ class SettingsRepository { required this.isSubtitlesAvailable, required this.start, required this.isLoop, - required this.manifest, + required this.playlist, required this.track, required this.chunk, required this.log @@ -66,7 +66,7 @@ class SettingsRepository { isSubtitlesAvailable: true, start: 0, isLoop: false, - manifest: 5000, + playlist: 5000, track: 3000, chunk: 3000, log: RepositoryLogType.info diff --git a/nut_player/example/lib/src/common/views/input_view.dart b/nut_player/example/lib/src/common/views/input_view.dart index ae462f3..16432d3 100644 --- a/nut_player/example/lib/src/common/views/input_view.dart +++ b/nut_player/example/lib/src/common/views/input_view.dart @@ -1,38 +1,106 @@ import 'package:flutter/cupertino.dart'; +import 'package:keyboard_actions/keyboard_actions.dart'; -class InputView extends StatelessWidget { - +class InputView extends StatefulWidget { final String title; final int value; final Function(int)? newSelection; - final TextEditingController _controller = TextEditingController(); - InputView(this.title, this.value, this.newSelection, {super.key}); + const InputView(this.title, this.value, this.newSelection, {super.key}); + + @override + State createState() => _InputViewState(); +} + +class _InputViewState extends State { + final TextEditingController _controller = TextEditingController(); + final FocusNode _nodeText = FocusNode(); + late String _textBefore; @override Widget build(BuildContext context) { - _controller.text = '$value'; + _controller.text = '${widget.value}'; + _textBefore = '${widget.value}'; return CupertinoListTile( - key: key, + key: widget.key, trailing: SizedBox( - width: 155, - child: CupertinoTextField( - minLines: 1, - maxLines: 1, - maxLength: 10, - autocorrect: false, - keyboardType: TextInputType.number, - controller: _controller, - decoration: null, - onSubmitted: (newText) { - var newTime = int.tryParse(newText); - if (newTime != null) { - newSelection?.call(newTime); - } - }, - ), + width: 155, + height: 30, + child: KeyboardActions( + tapOutsideBehavior: TapOutsideBehavior.translucentDismiss, + config: _buildConfig(context), + child: Container( + padding: const EdgeInsets.only(left: 12), + child: CupertinoTextField( + minLines: 1, + maxLines: 1, + maxLength: 10, + autocorrect: false, + focusNode: _nodeText, + decoration: null, + keyboardType: TextInputType.number, + controller: _controller, + onSubmitted: _onSubmit, + onTapOutside: (_) { + _controller.text = _textBefore; + }, + ), + ), + ) ), - title: Text(title, style: const TextStyle(decoration: TextDecoration.none, fontSize: 17)) + title: Text(widget.title, style: const TextStyle(decoration: TextDecoration.none, fontSize: 17))); + } + + void _onSubmit(String newText) { + _textBefore = newText; + var newTime = int.tryParse(newText); + if (newTime != null) { + widget.newSelection?.call(newTime); + } + } + + KeyboardActionsConfig _buildConfig(BuildContext context) { + return KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: CupertinoColors.white, + keyboardSeparatorColor: CupertinoColors.systemGrey4, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: _nodeText, + toolbarButtons: [ + (node) { + return TextFieldTapRegion( + child: Container( + width: MediaQuery.of(context).size.width - 14, + margin: const EdgeInsets.symmetric(horizontal: 7), + child: Row( + children: [ + CupertinoButton( + padding: const EdgeInsets.all(5), + onPressed: () { + _controller.text = _textBefore; + node.unfocus(); + }, + child: const Text("Отмена", style: TextStyle(fontWeight: FontWeight.w500)) + ), + const Spacer(), + CupertinoButton( + padding: const EdgeInsets.all(5), + onPressed: () { + _onSubmit(_controller.text); + node.unfocus(); + }, + child: const Text("Готово", style: TextStyle(fontWeight: FontWeight.w500)) + ) + ], + ), + ), + ); + } + ], + ), + ], ); } -} \ No newline at end of file +} diff --git a/nut_player/example/lib/src/features/player_screen/domain/bloc/playerview_bloc.dart b/nut_player/example/lib/src/features/player_screen/domain/bloc/playerview_bloc.dart index 563ace2..b4ad497 100644 --- a/nut_player/example/lib/src/features/player_screen/domain/bloc/playerview_bloc.dart +++ b/nut_player/example/lib/src/features/player_screen/domain/bloc/playerview_bloc.dart @@ -11,7 +11,14 @@ class PlayerViewBloc extends Bloc { final VideoPlayerController controller; PlayerViewBloc({required Provider provider, required SettingsRepository repository}): - controller = VideoPlayerController.provider(provider)..initialize(params: {'enablePip': true, 'enableAutostart': true}), + controller = VideoPlayerController.provider(provider) + ..initialize(params: {'enablePip': true, + 'enableAutostart': true, + 'timeouts': { + 'playlist': repository.playlist, + 'track': repository.track, + 'chunk': repository.chunk + }}), super(PlayerViewController.createFromRepository(repository)) { _onInitialize(repository); on(_onDismissEvent); diff --git a/nut_player/example/lib/src/features/settings_screen/domain/settings_bloc.dart b/nut_player/example/lib/src/features/settings_screen/domain/settings_bloc.dart index 59fd5e4..9012904 100644 --- a/nut_player/example/lib/src/features/settings_screen/domain/settings_bloc.dart +++ b/nut_player/example/lib/src/features/settings_screen/domain/settings_bloc.dart @@ -24,6 +24,9 @@ class SettingsBloc extends Bloc { on(_onSubsChangedEvent); on(_onStartPositionChangedEvent); on(_onLoopChangedEvent); + on(_onPlaylistTimeoutsChangedEvent); + on(_onTrackTimeoutsChangedEvent); + on(_onChunkTimeoutsChangedEvent); } _onDismissEvent(DismissEvent event, Emitter emit) { @@ -58,6 +61,27 @@ class SettingsBloc extends Bloc { _repository.start = event.value; } + _onPlaylistTimeoutsChangedEvent(PlaylistTimeoutsChangedEvent event, Emitter emit) { + final settingsState = state; + if (settingsState is! SettingsInitialState) { return; } + + _repository.playlist = event.playlist; + } + + _onTrackTimeoutsChangedEvent(TrackTimeoutsChangedEvent event, Emitter emit) { + final settingsState = state; + if (settingsState is! SettingsInitialState) { return; } + + _repository.track = event.track; + } + + _onChunkTimeoutsChangedEvent(ChunkTimeoutsChangedEvent event, Emitter emit) { + final settingsState = state; + if (settingsState is! SettingsInitialState) { return; } + + _repository.chunk = event.chunk; + } + _onBrightnessChangedEvent(BrightnessChangedEvent event, Emitter emit) {} _onQualityChangedEvent(QualityChangedEvent event, Emitter emit) {} _onSubsChangedEvent(SubsChangedEvent event, Emitter emit) {} @@ -69,7 +93,19 @@ class SettingsBloc extends Bloc { _onFullscreenChangedEvent(FullscreenChangedEvent event, Emitter emit) {} _onColorChangedEvent(ColorChangedEvent event, Emitter emit) {} - int get currentPosition => _repository.start; + int currentNumericValue(NumericOptionData setting) { + if (setting.key == const Key('PlaybackOptionStartPositionID')) { + return _repository.start; + } else if (setting.key == const Key('TimeoutsOptionManifestID')) { + return _repository.playlist; + } else if (setting.key == const Key('TimeoutsOptionTrackID')) { + return _repository.track; + } else if (setting.key == const Key('TimeoutsOptionChunkID')) { + return _repository.chunk; + } else { + return 0; + } + } int selectedIndex(OptionDataContainer setting) { if (setting.key == const Key('PlaybackOptionSpeedID')) { diff --git a/nut_player/example/lib/src/features/settings_screen/domain/settings_event.dart b/nut_player/example/lib/src/features/settings_screen/domain/settings_event.dart index c955713..d5ea97c 100644 --- a/nut_player/example/lib/src/features/settings_screen/domain/settings_event.dart +++ b/nut_player/example/lib/src/features/settings_screen/domain/settings_event.dart @@ -68,4 +68,22 @@ class LoopChangedEvent extends SettingsEvent { class StartPositionChangedEvent extends SettingsEvent { final int value; StartPositionChangedEvent(this.value); +} + +class PlaylistTimeoutsChangedEvent extends SettingsEvent { + final int playlist; + + PlaylistTimeoutsChangedEvent(this.playlist); +} + +class TrackTimeoutsChangedEvent extends SettingsEvent { + final int track; + + TrackTimeoutsChangedEvent(this.track); +} + +class ChunkTimeoutsChangedEvent extends SettingsEvent { + final int chunk; + + ChunkTimeoutsChangedEvent(this.chunk); } \ No newline at end of file diff --git a/nut_player/example/lib/src/features/settings_screen/domain/settings_state.dart b/nut_player/example/lib/src/features/settings_screen/domain/settings_state.dart index 52d359c..4582cde 100644 --- a/nut_player/example/lib/src/features/settings_screen/domain/settings_state.dart +++ b/nut_player/example/lib/src/features/settings_screen/domain/settings_state.dart @@ -30,7 +30,7 @@ class SettingsInitialState extends SettingsState { final bool isLoop; - final int manifest; + final int playlist; final int track; final int chunk; @@ -51,7 +51,7 @@ class SettingsInitialState extends SettingsState { required this.isSubtitlesAvailable, required this.start, required this.isLoop, - required this.manifest, + required this.playlist, required this.track, required this.chunk, required this.log @@ -76,7 +76,7 @@ class SettingsInitialState extends SettingsState { bool? isLoop, - int? manifest, + int? playlist, int? track, int? chunk, @@ -97,7 +97,7 @@ class SettingsInitialState extends SettingsState { isSubtitlesAvailable: isSubtitlesAvailable ?? this.isSubtitlesAvailable, start: start ?? this.start, isLoop: isLoop ?? this.isLoop, - manifest: manifest ?? this.manifest, + playlist: playlist ?? this.playlist, track: track ?? this.track, chunk: chunk ?? this.chunk, log: log ?? this.log @@ -120,7 +120,7 @@ class SettingsInitialState extends SettingsState { isSubtitlesAvailable: repository.isSubtitlesAvailable, start: repository.start, isLoop: repository.isLoop, - manifest: repository.manifest, + playlist: repository.playlist, track: repository.track, chunk: repository.chunk, log: _logType(repository.log) @@ -309,10 +309,25 @@ class SettingsInitialState extends SettingsState { static const extraOption = OptionData(key: Key('ExtraOptionLoopID'), title: 'Зацикленность'); - static const timeoutsOptions = [ - NumericOptionData(key: Key('TimeoutsOptionManifestID'), title: 'Манифест (мс)', value: 5000), - NumericOptionData(key: Key('TimeoutsOptionTrackID'), title: 'Трек (мс)', value: 3000), - NumericOptionData(key: Key('TimeoutsOptionChunkID'), title: 'Сегмент (мс)', value: 3000) + static List timeoutsOptions = [ + NumericOptionData( + key: const Key('TimeoutsOptionManifestID'), + title: 'Манифест (мс)', + value: 5000, + onChange: (value) => PlaylistTimeoutsChangedEvent(value) + ), + NumericOptionData( + key: const Key('TimeoutsOptionTrackID'), + title: 'Трек (мс)', + value: 3000, + onChange: (value) => TrackTimeoutsChangedEvent(value) + ), + NumericOptionData( + key: const Key('TimeoutsOptionChunkID'), + title: 'Сегмент (мс)', + value: 3000, + onChange: (value) => ChunkTimeoutsChangedEvent(value) + ) ]; static const logOptions = { diff --git a/nut_player/example/lib/src/features/settings_screen/presentation/settings_view.dart b/nut_player/example/lib/src/features/settings_screen/presentation/settings_view.dart index 1fdfa9c..554f321 100644 --- a/nut_player/example/lib/src/features/settings_screen/presentation/settings_view.dart +++ b/nut_player/example/lib/src/features/settings_screen/presentation/settings_view.dart @@ -103,9 +103,7 @@ class SettingsView extends StatelessWidget { style: const TextStyle(decoration: TextDecoration.none, color: CupertinoColors.systemGrey, fontSize: 13, fontWeight: FontWeight.w400) ), ), - children: [...SettingsInitialState.timeoutsOptions.map((setting) { - return InputView(setting.title, setting.value, (_) {}); - }).toList()] + children: _buildSettingsWidgets(SettingsInitialState.timeoutsOptions, bloc) ), // Логирование @@ -223,9 +221,10 @@ class SettingsView extends StatelessWidget { } else if (setting is OptionData) { return ToggleView(setting); } else if (setting is NumericOptionData) { + final currentValue = bloc.currentNumericValue(setting); return InputView( setting.title, - bloc.currentPosition, + currentValue, (newTime) { final event = setting.onChange?.call(newTime); if (event != null) { diff --git a/nut_player/example/pubspec.yaml b/nut_player/example/pubspec.yaml index 94bbbee..0d848d9 100644 --- a/nut_player/example/pubspec.yaml +++ b/nut_player/example/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: sdk: flutter flutter_bloc: ^8.0.1 package_info_plus: ^4.1.0 + keyboard_actions: ^4.2.0 nut_player: diff --git a/nut_player/lib/src/controller/video_player_controller.dart b/nut_player/lib/src/controller/video_player_controller.dart index cd6e9be..e44dd0b 100644 --- a/nut_player/lib/src/controller/video_player_controller.dart +++ b/nut_player/lib/src/controller/video_player_controller.dart @@ -117,7 +117,7 @@ class VideoPlayerController extends ValueNotifier { } } - Future initialize({Map? params}) async { + Future initialize({Map? params}) async { const bool allowBackgroundPlayback = false; if (!allowBackgroundPlayback && _lifeCycleObserver == null) { _lifeCycleObserver = _VideoAppLifeCycleObserver(this); diff --git a/nut_player_android/lib/src/nut_player_android.dart b/nut_player_android/lib/src/nut_player_android.dart index 6ec03c9..875cfcf 100644 --- a/nut_player_android/lib/src/nut_player_android.dart +++ b/nut_player_android/lib/src/nut_player_android.dart @@ -45,7 +45,7 @@ class NutPlayerAndroidPlatform extends NutPlayerPlatform { } @override - Future create({required PlatformPlayerContent content, Map? params}) async { + Future create({required PlatformPlayerContent content, Map? params}) async { final playerId = await _pluginChannel.invokeMethod("pluginCreate", [content.toJson(), params]); return playerId; } diff --git a/nut_player_ios/ios/Classes/NutPlayerIosPlugin.swift b/nut_player_ios/ios/Classes/NutPlayerIosPlugin.swift index 262139a..9f57a11 100644 --- a/nut_player_ios/ios/Classes/NutPlayerIosPlugin.swift +++ b/nut_player_ios/ios/Classes/NutPlayerIosPlugin.swift @@ -209,7 +209,7 @@ public class NutPlayerIosPlugin: NSObject, FlutterPlugin, NutPlayerViewFactoryDe let channel = FlutterEventChannel(name: "tech.nut/videoPlayer/videoEvents/\(playerId)", binaryMessenger: self.registrar.messenger()) let player = PlayerFlutterPlatform(rawContent, - parameters: arguments[safe: 1] as? [String: Bool], + parameters: arguments[safe: 1] as? [String: Any], playerId: playerId, channel: channel) diff --git a/nut_player_ios/ios/Classes/NutPlayerViewFactory.swift b/nut_player_ios/ios/Classes/NutPlayerViewFactory.swift index 97e9125..fe73f80 100644 --- a/nut_player_ios/ios/Classes/NutPlayerViewFactory.swift +++ b/nut_player_ios/ios/Classes/NutPlayerViewFactory.swift @@ -11,7 +11,7 @@ import NutPlayer protocol NutPlayerViewFactoryDelegate { - typealias Parameters = [String: Bool] + typealias Parameters = [String: Any] func playerInstance(for id: Int64) -> NutPlayer? func playerParams(for id: Int64) -> Parameters? @@ -42,13 +42,13 @@ final class NutPlayerViewFactory: NSObject, FlutterPlatformViewFactory { let settings: NutPlayerSkinPlugin.Settings if let parameters { settings = NutPlayerSkinPlugin.Settings( - onPip: parameters["enablePip"] ?? false + onPip: parameters["enablePip"] as? Bool ?? false ? { onEnter in player.pipController.value >>- { onEnter ? $0.start() : $0.stop() } } : nil, - onFullscreen: parameters["enableFullscreen"] ?? false + onFullscreen: parameters["enableFullscreen"] as? Bool ?? false ? { onEnter in print("Fullscreen") } : nil diff --git a/nut_player_ios/ios/Classes/PlayerFlutterPlatform.swift b/nut_player_ios/ios/Classes/PlayerFlutterPlatform.swift index 88ddc69..3afef94 100644 --- a/nut_player_ios/ios/Classes/PlayerFlutterPlatform.swift +++ b/nut_player_ios/ios/Classes/PlayerFlutterPlatform.swift @@ -33,11 +33,11 @@ final class PlayerFlutterPlatform: NSObject, FlutterStreamHandler { private var cancellable = Set() private var playerContent: PlayerContent? - let parameters: [String: Bool]? + let parameters: [String: Any]? // MARK: - Init - init(_ rawContent: String, parameters: [String: Bool]?, playerId: Int64, channel: FlutterEventChannel) { + init(_ rawContent: String, parameters: [String: Any]?, playerId: Int64, channel: FlutterEventChannel) { self.playerId = playerId self.channel = channel @@ -132,12 +132,13 @@ final class PlayerFlutterPlatform: NSObject, FlutterStreamHandler { let autostart: Bool if let parameters { - autostart = parameters["enableAutostart"] ?? false + autostart = parameters["enableAutostart"] as? Bool ?? false } else { autostart = false } - player.load(provider: CommonProvider(content: content), autoplay: autostart) + let timeouts = self.createTimeouts(from: parameters) + player.load(provider: CommonProvider(content: content), timeouts: timeouts, autoplay: autostart) } else { self.lastEvent = .error let flutterError = FlutterError(code: "NutPlayerError", @@ -147,6 +148,20 @@ final class PlayerFlutterPlatform: NSObject, FlutterStreamHandler { } } + private func createTimeouts(from params: [String: Any]?) -> PlayerTimeouts { + let timeouts = parameters?["timeouts"] as? [String: Any] + + var playlistTimeout = timeouts?["playlist"] as? TimeInterval ?? 5000 + if playlistTimeout <= 0 { + playlistTimeout = 5000 + } + var trackTimeout = timeouts?["track"] as? TimeInterval ?? 3000 + if trackTimeout <= 0 { + trackTimeout = 3000 + } + return PlayerTimeouts(playlist: playlistTimeout, track: trackTimeout) + } + // MARK: - Interface var currentTime: Double { self.player?.currentTime.value ?? 0.0 } diff --git a/nut_player_ios/lib/src/nut_player_ios.dart b/nut_player_ios/lib/src/nut_player_ios.dart index c4d47a6..72eccfd 100644 --- a/nut_player_ios/lib/src/nut_player_ios.dart +++ b/nut_player_ios/lib/src/nut_player_ios.dart @@ -46,7 +46,7 @@ class NutPlayerIosPlatform extends NutPlayerPlatform { } @override - Future create({required PlatformPlayerContent content, Map? params}) async { + Future create({required PlatformPlayerContent content, Map? params}) async { final playerId = await _pluginChannel.invokeMethod("pluginCreate", [content.toJson(), params]); return playerId; } diff --git a/nut_player_platform_interface/lib/src/nut_player_platform.dart b/nut_player_platform_interface/lib/src/nut_player_platform.dart index 5a3ee04..16055a5 100644 --- a/nut_player_platform_interface/lib/src/nut_player_platform.dart +++ b/nut_player_platform_interface/lib/src/nut_player_platform.dart @@ -47,7 +47,7 @@ abstract class NutPlayerPlatform extends PlatformInterface { } /// Creates an instance of a video player and returns its PlayerId. - Future create({required PlatformPlayerContent content, Map? params}) { + Future create({required PlatformPlayerContent content, Map? params}) { throw UnimplementedError('create() has not been implemented.'); }