video: disable stream selection when irrelevant

This commit is contained in:
Thibault Deckers 2021-06-22 18:10:09 +09:00
parent 7bedca9537
commit e40770b1c4
4 changed files with 72 additions and 62 deletions

View file

@ -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,

View file

@ -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();

View file

@ -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() {

View file

@ -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),
);
}