PSDK-1029 - Задержки
This commit is contained in:
committed by
Jura Shikin
parent
4ade88a340
commit
d8810095ac
@@ -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
|
||||
|
||||
@@ -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<InputView> createState() => _InputViewState();
|
||||
}
|
||||
|
||||
class _InputViewState extends State<InputView> {
|
||||
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))
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,14 @@ class PlayerViewBloc extends Bloc<PlayerViewEvent, PlayerViewState> {
|
||||
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<DismissEvent>(_onDismissEvent);
|
||||
|
||||
@@ -24,6 +24,9 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
|
||||
on<SubsChangedEvent>(_onSubsChangedEvent);
|
||||
on<StartPositionChangedEvent>(_onStartPositionChangedEvent);
|
||||
on<LoopChangedEvent>(_onLoopChangedEvent);
|
||||
on<PlaylistTimeoutsChangedEvent>(_onPlaylistTimeoutsChangedEvent);
|
||||
on<TrackTimeoutsChangedEvent>(_onTrackTimeoutsChangedEvent);
|
||||
on<ChunkTimeoutsChangedEvent>(_onChunkTimeoutsChangedEvent);
|
||||
}
|
||||
|
||||
_onDismissEvent(DismissEvent event, Emitter<SettingsState> emit) {
|
||||
@@ -58,6 +61,27 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
|
||||
_repository.start = event.value;
|
||||
}
|
||||
|
||||
_onPlaylistTimeoutsChangedEvent(PlaylistTimeoutsChangedEvent event, Emitter<SettingsState> emit) {
|
||||
final settingsState = state;
|
||||
if (settingsState is! SettingsInitialState) { return; }
|
||||
|
||||
_repository.playlist = event.playlist;
|
||||
}
|
||||
|
||||
_onTrackTimeoutsChangedEvent(TrackTimeoutsChangedEvent event, Emitter<SettingsState> emit) {
|
||||
final settingsState = state;
|
||||
if (settingsState is! SettingsInitialState) { return; }
|
||||
|
||||
_repository.track = event.track;
|
||||
}
|
||||
|
||||
_onChunkTimeoutsChangedEvent(ChunkTimeoutsChangedEvent event, Emitter<SettingsState> emit) {
|
||||
final settingsState = state;
|
||||
if (settingsState is! SettingsInitialState) { return; }
|
||||
|
||||
_repository.chunk = event.chunk;
|
||||
}
|
||||
|
||||
_onBrightnessChangedEvent(BrightnessChangedEvent event, Emitter<SettingsState> emit) {}
|
||||
_onQualityChangedEvent(QualityChangedEvent event, Emitter<SettingsState> emit) {}
|
||||
_onSubsChangedEvent(SubsChangedEvent event, Emitter<SettingsState> emit) {}
|
||||
@@ -69,7 +93,19 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
|
||||
_onFullscreenChangedEvent(FullscreenChangedEvent event, Emitter<SettingsState> emit) {}
|
||||
_onColorChangedEvent(ColorChangedEvent event, Emitter<SettingsState> 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')) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<NumericOptionData> 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 = {
|
||||
|
||||
@@ -103,9 +103,7 @@ class SettingsView extends StatelessWidget {
|
||||
style: const TextStyle(decoration: TextDecoration.none, color: CupertinoColors.systemGrey, fontSize: 13, fontWeight: FontWeight.w400)
|
||||
),
|
||||
),
|
||||
children: <Widget>[...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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -117,7 +117,7 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initialize({Map<String, bool>? params}) async {
|
||||
Future<void> initialize({Map<String, Object>? params}) async {
|
||||
const bool allowBackgroundPlayback = false;
|
||||
if (!allowBackgroundPlayback && _lifeCycleObserver == null) {
|
||||
_lifeCycleObserver = _VideoAppLifeCycleObserver(this);
|
||||
|
||||
@@ -45,7 +45,7 @@ class NutPlayerAndroidPlatform extends NutPlayerPlatform {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlayerId> create({required PlatformPlayerContent content, Map<String, bool>? params}) async {
|
||||
Future<PlayerId> create({required PlatformPlayerContent content, Map<String, Object>? params}) async {
|
||||
final playerId = await _pluginChannel.invokeMethod("pluginCreate", [content.toJson(), params]);
|
||||
return playerId;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,11 +33,11 @@ final class PlayerFlutterPlatform: NSObject, FlutterStreamHandler {
|
||||
private var cancellable = Set<AnyCancellable>()
|
||||
|
||||
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 }
|
||||
|
||||
@@ -46,7 +46,7 @@ class NutPlayerIosPlatform extends NutPlayerPlatform {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlayerId> create({required PlatformPlayerContent content, Map<String, bool>? params}) async {
|
||||
Future<PlayerId> create({required PlatformPlayerContent content, Map<String, Object>? params}) async {
|
||||
final playerId = await _pluginChannel.invokeMethod("pluginCreate", [content.toJson(), params]);
|
||||
return playerId;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ abstract class NutPlayerPlatform extends PlatformInterface {
|
||||
}
|
||||
|
||||
/// Creates an instance of a video player and returns its PlayerId.
|
||||
Future<PlayerId> create({required PlatformPlayerContent content, Map<String, bool>? params}) {
|
||||
Future<PlayerId> create({required PlatformPlayerContent content, Map<String, Object>? params}) {
|
||||
throw UnimplementedError('create() has not been implemented.');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user