video: disable stream selection when irrelevant
This commit is contained in:
parent
7bedca9537
commit
e40770b1c4
4 changed files with 72 additions and 62 deletions
|
@ -14,6 +14,7 @@ import 'package:aves/widgets/common/fx/blurred.dart';
|
|||
import 'package:aves/widgets/common/fx/borders.dart';
|
||||
import 'package:aves/widgets/viewer/overlay/common.dart';
|
||||
import 'package:aves/widgets/viewer/video/controller.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -237,6 +238,21 @@ class _ButtonRow extends StatelessWidget {
|
|||
Widget _buildOverlayButton(BuildContext context, VideoAction action) {
|
||||
late Widget child;
|
||||
void onPressed() => onActionSelected(action);
|
||||
|
||||
ValueListenableBuilder<bool> _buildFromListenable(ValueListenable<bool>? enabledNotifier) {
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: enabledNotifier ?? ValueNotifier(false),
|
||||
builder: (context, canDo, child) {
|
||||
return IconButton(
|
||||
icon: child!,
|
||||
onPressed: canDo ? onPressed : null,
|
||||
tooltip: action.getText(context),
|
||||
);
|
||||
},
|
||||
child: Icon(action.getIcon()),
|
||||
);
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case VideoAction.togglePlay:
|
||||
child = _PlayToggler(
|
||||
|
@ -245,20 +261,12 @@ class _ButtonRow extends StatelessWidget {
|
|||
);
|
||||
break;
|
||||
case VideoAction.captureFrame:
|
||||
child = ValueListenableBuilder<bool>(
|
||||
valueListenable: controller?.renderingVideoNotifier ?? ValueNotifier(false),
|
||||
builder: (context, canDo, child) {
|
||||
return IconButton(
|
||||
icon: child!,
|
||||
onPressed: canDo ? onPressed : null,
|
||||
tooltip: action.getText(context),
|
||||
);
|
||||
},
|
||||
child: Icon(action.getIcon()),
|
||||
);
|
||||
child = _buildFromListenable(controller?.renderingVideoNotifier);
|
||||
break;
|
||||
case VideoAction.selectStreams:
|
||||
child = _buildFromListenable(controller?.canSelectStreamNotifier);
|
||||
break;
|
||||
case VideoAction.replay10:
|
||||
case VideoAction.selectStreams:
|
||||
case VideoAction.setSpeed:
|
||||
child = IconButton(
|
||||
icon: Icon(action.getIcon()),
|
||||
|
@ -277,7 +285,21 @@ class _ButtonRow extends StatelessWidget {
|
|||
}
|
||||
|
||||
PopupMenuEntry<VideoAction> _buildPopupMenuItem(BuildContext context, VideoAction action) {
|
||||
var enabled = true;
|
||||
late final enabled;
|
||||
switch (action) {
|
||||
case VideoAction.togglePlay:
|
||||
case VideoAction.replay10:
|
||||
case VideoAction.setSpeed:
|
||||
enabled = true;
|
||||
break;
|
||||
case VideoAction.captureFrame:
|
||||
enabled = controller?.renderingVideoNotifier.value ?? false;
|
||||
break;
|
||||
case VideoAction.selectStreams:
|
||||
enabled = controller?.canSelectStreamNotifier.value ?? false;
|
||||
break;
|
||||
}
|
||||
|
||||
Widget? child;
|
||||
switch (action) {
|
||||
case VideoAction.togglePlay:
|
||||
|
@ -287,15 +309,13 @@ class _ButtonRow extends StatelessWidget {
|
|||
);
|
||||
break;
|
||||
case VideoAction.captureFrame:
|
||||
enabled = controller?.renderingVideoNotifier.value ?? false;
|
||||
child = MenuRow(text: action.getText(context), icon: action.getIcon());
|
||||
break;
|
||||
case VideoAction.replay10:
|
||||
case VideoAction.selectStreams:
|
||||
case VideoAction.setSpeed:
|
||||
child = MenuRow(text: action.getText(context), icon: action.getIcon());
|
||||
break;
|
||||
}
|
||||
|
||||
return PopupMenuItem(
|
||||
value: action,
|
||||
enabled: enabled,
|
||||
|
|
|
@ -27,8 +27,6 @@ abstract class AvesVideoController {
|
|||
|
||||
Stream<VideoStatus> get statusStream;
|
||||
|
||||
ValueNotifier<bool> get renderingVideoNotifier;
|
||||
|
||||
bool get isReady;
|
||||
|
||||
bool get isPlaying => status == VideoStatus.playing;
|
||||
|
@ -43,6 +41,10 @@ abstract class AvesVideoController {
|
|||
|
||||
Stream<String?> get timedTextStream;
|
||||
|
||||
ValueNotifier<bool> get renderingVideoNotifier;
|
||||
|
||||
ValueNotifier<bool> get canSelectStreamNotifier;
|
||||
|
||||
ValueNotifier<double> get sarNotifier;
|
||||
|
||||
double get speed;
|
||||
|
@ -57,7 +59,7 @@ abstract class AvesVideoController {
|
|||
|
||||
Future<StreamSummary?> getSelectedStream(StreamType type);
|
||||
|
||||
Map<StreamSummary, bool> get streams;
|
||||
List<StreamSummary> get streams;
|
||||
|
||||
Future<Uint8List> captureFrame();
|
||||
|
||||
|
|
|
@ -23,9 +23,6 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
|
|||
final AChangeNotifier _completedNotifier = AChangeNotifier();
|
||||
Offset _macroBlockCrop = Offset.zero;
|
||||
final List<StreamSummary> _streams = [];
|
||||
final ValueNotifier<StreamSummary?> _selectedVideoStream = ValueNotifier(null);
|
||||
final ValueNotifier<StreamSummary?> _selectedAudioStream = ValueNotifier(null);
|
||||
final ValueNotifier<StreamSummary?> _selectedTextStream = ValueNotifier(null);
|
||||
Timer? _initialPlayTimer;
|
||||
double _speed = 1;
|
||||
|
||||
|
@ -41,6 +38,9 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
|
|||
@override
|
||||
final ValueNotifier<bool> renderingVideoNotifier = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
final ValueNotifier<bool> canSelectStreamNotifier = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
final ValueNotifier<double> sarNotifier = ValueNotifier(1);
|
||||
|
||||
|
@ -177,10 +177,12 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
|
|||
_instance.applyOptions(options);
|
||||
}
|
||||
|
||||
void _fetchSelectedStreams() async {
|
||||
void _fetchStreams() async {
|
||||
final mediaInfo = await _instance.getInfo();
|
||||
if (!mediaInfo.containsKey(Keys.streams)) return;
|
||||
|
||||
var videoStreamCount = 0, audioStreamCount = 0, textStreamCount = 0;
|
||||
|
||||
_streams.clear();
|
||||
final allStreams = (mediaInfo[Keys.streams] as List).cast<Map>();
|
||||
allStreams.forEach((stream) {
|
||||
|
@ -195,26 +197,25 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
|
|||
width: stream[Keys.width] as int?,
|
||||
height: stream[Keys.height] as int?,
|
||||
));
|
||||
switch (type) {
|
||||
case StreamType.video:
|
||||
videoStreamCount++;
|
||||
break;
|
||||
case StreamType.audio:
|
||||
audioStreamCount++;
|
||||
break;
|
||||
case StreamType.text:
|
||||
textStreamCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
StreamSummary? _getSelectedStream(String selectedIndexKey) {
|
||||
final indexString = mediaInfo[selectedIndexKey];
|
||||
if (indexString != null) {
|
||||
final index = int.tryParse(indexString);
|
||||
if (index != null && index != -1) {
|
||||
return _streams.firstWhereOrNull((stream) => stream.index == index);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
canSelectStreamNotifier.value = videoStreamCount > 1 || audioStreamCount > 1 || textStreamCount > 0;
|
||||
|
||||
_selectedVideoStream.value = _getSelectedStream(Keys.selectedVideoStream);
|
||||
_selectedAudioStream.value = _getSelectedStream(Keys.selectedAudioStream);
|
||||
_selectedTextStream.value = _getSelectedStream(Keys.selectedTextStream);
|
||||
|
||||
if (_selectedVideoStream.value != null) {
|
||||
final streamIndex = _selectedVideoStream.value!.index;
|
||||
final selectedVideo = await getSelectedStream(StreamType.video);
|
||||
if (selectedVideo != null) {
|
||||
final streamIndex = selectedVideo.index;
|
||||
final streamInfo = allStreams.firstWhereOrNull((stream) => stream[Keys.index] == streamIndex);
|
||||
if (streamInfo != null) {
|
||||
final num = streamInfo[Keys.sarNum] ?? 0;
|
||||
|
@ -226,7 +227,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
|
|||
|
||||
void _onValueChanged() {
|
||||
if (_instance.state == FijkState.prepared && _streams.isEmpty) {
|
||||
_fetchSelectedStreams();
|
||||
_fetchStreams();
|
||||
}
|
||||
_valueStreamController.add(_instance.value);
|
||||
}
|
||||
|
@ -301,17 +302,6 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
|
|||
// TODO TLAD [video] bug: setting speed fails when there is no audio stream or audio is disabled
|
||||
void _applySpeed() => _instance.setSpeed(speed);
|
||||
|
||||
ValueNotifier<StreamSummary?> selectedStreamNotifier(StreamType type) {
|
||||
switch (type) {
|
||||
case StreamType.video:
|
||||
return _selectedVideoStream;
|
||||
case StreamType.audio:
|
||||
return _selectedAudioStream;
|
||||
case StreamType.text:
|
||||
return _selectedTextStream;
|
||||
}
|
||||
}
|
||||
|
||||
// When a stream is selected, the video accelerates to catch up with it.
|
||||
// The duration of this acceleration phase depends on the player `min-frames` parameter.
|
||||
// Calling `seekTo` after stream de/selection is a workaround to:
|
||||
|
@ -325,11 +315,9 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
|
|||
final newIndex = selected.index;
|
||||
if (newIndex != null) {
|
||||
await _instance.selectTrack(newIndex);
|
||||
selectedStreamNotifier(type).value = selected;
|
||||
}
|
||||
} else if (current != null) {
|
||||
await _instance.deselectTrack(current.index!);
|
||||
selectedStreamNotifier(type).value = null;
|
||||
}
|
||||
await seekTo(currentPosition);
|
||||
}
|
||||
|
@ -342,10 +330,7 @@ class IjkPlayerAvesVideoController extends AvesVideoController {
|
|||
}
|
||||
|
||||
@override
|
||||
Map<StreamSummary, bool> get streams {
|
||||
final selectedIndices = {_selectedVideoStream, _selectedAudioStream, _selectedTextStream}.map((v) => v.value?.index).toSet();
|
||||
return Map.fromEntries(_streams.map((stream) => MapEntry(stream, selectedIndices.contains(stream.index))));
|
||||
}
|
||||
List<StreamSummary> get streams => _streams;
|
||||
|
||||
@override
|
||||
Future<Uint8List> captureFrame() {
|
||||
|
|
|
@ -120,17 +120,20 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
}
|
||||
|
||||
Future<void> _showStreamSelectionDialog(BuildContext context, AvesVideoController controller) async {
|
||||
final selectedStreams = await showDialog<Map<StreamType, StreamSummary>>(
|
||||
final streams = controller.streams;
|
||||
final currentSelectedStreams = await Future.wait(StreamType.values.map(controller.getSelectedStream));
|
||||
final currentSelectedIndices = currentSelectedStreams.where((v) => v != null).cast<StreamSummary>().map((v) => v.index).toSet();
|
||||
|
||||
final userSelectedStreams = await showDialog<Map<StreamType, StreamSummary>>(
|
||||
context: context,
|
||||
builder: (context) => VideoStreamSelectionDialog(
|
||||
streams: controller.streams,
|
||||
streams: Map.fromEntries(streams.map((stream) => MapEntry(stream, currentSelectedIndices.contains(stream.index)))),
|
||||
),
|
||||
);
|
||||
if (selectedStreams == null || selectedStreams.isEmpty) return;
|
||||
if (userSelectedStreams == null || userSelectedStreams.isEmpty) return;
|
||||
|
||||
// TODO TLAD [video] get stream list & guess default selected streams, when the controller is not initialized yet
|
||||
await Future.forEach<MapEntry<StreamType, StreamSummary>>(
|
||||
selectedStreams.entries,
|
||||
userSelectedStreams.entries,
|
||||
(kv) => controller.selectStream(kv.key, kv.value),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue