diff --git a/lib/widgets/viewer/overlay/bottom/video.dart b/lib/widgets/viewer/overlay/bottom/video.dart index ff21a00f1..d003e90ce 100644 --- a/lib/widgets/viewer/overlay/bottom/video.dart +++ b/lib/widgets/viewer/overlay/bottom/video.dart @@ -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 _buildFromListenable(ValueListenable? enabledNotifier) { + return ValueListenableBuilder( + 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( - 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 _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, diff --git a/lib/widgets/viewer/video/controller.dart b/lib/widgets/viewer/video/controller.dart index 5a32c1b28..fc79bef15 100644 --- a/lib/widgets/viewer/video/controller.dart +++ b/lib/widgets/viewer/video/controller.dart @@ -27,8 +27,6 @@ abstract class AvesVideoController { Stream get statusStream; - ValueNotifier get renderingVideoNotifier; - bool get isReady; bool get isPlaying => status == VideoStatus.playing; @@ -43,6 +41,10 @@ abstract class AvesVideoController { Stream get timedTextStream; + ValueNotifier get renderingVideoNotifier; + + ValueNotifier get canSelectStreamNotifier; + ValueNotifier get sarNotifier; double get speed; @@ -57,7 +59,7 @@ abstract class AvesVideoController { Future getSelectedStream(StreamType type); - Map get streams; + List get streams; Future captureFrame(); diff --git a/lib/widgets/viewer/video/fijkplayer.dart b/lib/widgets/viewer/video/fijkplayer.dart index ebd0d6761..a281ccfab 100644 --- a/lib/widgets/viewer/video/fijkplayer.dart +++ b/lib/widgets/viewer/video/fijkplayer.dart @@ -23,9 +23,6 @@ class IjkPlayerAvesVideoController extends AvesVideoController { final AChangeNotifier _completedNotifier = AChangeNotifier(); Offset _macroBlockCrop = Offset.zero; final List _streams = []; - final ValueNotifier _selectedVideoStream = ValueNotifier(null); - final ValueNotifier _selectedAudioStream = ValueNotifier(null); - final ValueNotifier _selectedTextStream = ValueNotifier(null); Timer? _initialPlayTimer; double _speed = 1; @@ -41,6 +38,9 @@ class IjkPlayerAvesVideoController extends AvesVideoController { @override final ValueNotifier renderingVideoNotifier = ValueNotifier(false); + @override + final ValueNotifier canSelectStreamNotifier = ValueNotifier(false); + @override final ValueNotifier 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(); 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 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 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 get streams => _streams; @override Future captureFrame() { diff --git a/lib/widgets/viewer/video_action_delegate.dart b/lib/widgets/viewer/video_action_delegate.dart index 1af4ece2e..8286cb566 100644 --- a/lib/widgets/viewer/video_action_delegate.dart +++ b/lib/widgets/viewer/video_action_delegate.dart @@ -120,17 +120,20 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix } Future _showStreamSelectionDialog(BuildContext context, AvesVideoController controller) async { - final selectedStreams = await showDialog>( + final streams = controller.streams; + final currentSelectedStreams = await Future.wait(StreamType.values.map(controller.getSelectedStream)); + final currentSelectedIndices = currentSelectedStreams.where((v) => v != null).cast().map((v) => v.index).toSet(); + + final userSelectedStreams = await showDialog>( 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>( - selectedStreams.entries, + userSelectedStreams.entries, (kv) => controller.selectStream(kv.key, kv.value), ); }