diff --git a/lib/model/video/keys.dart b/lib/model/video/keys.dart index ebdefad36..773f1809e 100644 --- a/lib/model/video/keys.dart +++ b/lib/model/video/keys.dart @@ -35,6 +35,9 @@ class Keys { static const sampleRate = 'sample_rate'; static const sarDen = 'sar_den'; static const sarNum = 'sar_num'; + static const selectedAudioStream = 'audio'; + static const selectedTextStream = 'timedtext'; + static const selectedVideoStream = 'video'; static const startMicros = 'start_us'; static const statisticsTags = '_statistics_tags'; static const statisticsWritingApp = '_statistics_writing_app'; @@ -43,6 +46,7 @@ class Keys { static const tbrDen = 'tbr_den'; static const tbrNum = 'tbr_num'; static const streamType = 'type'; + static const title = 'title'; static const track = 'track'; static const width = 'width'; } diff --git a/lib/model/video/metadata.dart b/lib/model/video/metadata.dart index d04190340..e32faf46e 100644 --- a/lib/model/video/metadata.dart +++ b/lib/model/video/metadata.dart @@ -96,11 +96,14 @@ class VideoMetadataFormatter { case Keys.handlerName: case Keys.index: case Keys.sarNum: + case Keys.selectedAudioStream: + case Keys.selectedTextStream: + case Keys.selectedVideoStream: + case Keys.statisticsTags: case Keys.streams: + case Keys.streamType: case Keys.tbrNum: case Keys.tbrDen: - case Keys.statisticsTags: - case Keys.streamType: break; case Keys.androidCaptureFramerate: final captureFps = double.parse(value); diff --git a/lib/widgets/common/video/fijkplayer.dart b/lib/widgets/common/video/fijkplayer.dart index 80c7870b8..18d2aba52 100644 --- a/lib/widgets/common/video/fijkplayer.dart +++ b/lib/widgets/common/video/fijkplayer.dart @@ -4,10 +4,14 @@ import 'dart:ui'; import 'package:aves/model/entry.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/video_loop_mode.dart'; +import 'package:aves/model/video/keys.dart'; +import 'package:aves/model/video/metadata.dart'; import 'package:aves/utils/change_notifier.dart'; import 'package:aves/widgets/common/video/controller.dart'; import 'package:fijkplayer/fijkplayer.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:tuple/tuple.dart'; class IjkPlayerAvesVideoController extends AvesVideoController { FijkPlayer _instance; @@ -15,10 +19,16 @@ class IjkPlayerAvesVideoController extends AvesVideoController { final StreamController _valueStreamController = StreamController.broadcast(); 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); + final ValueNotifier> _sar = ValueNotifier(Tuple2(1, 1)); Stream get _valueStream => _valueStreamController.stream; IjkPlayerAvesVideoController(AvesEntry entry) { + FijkLog.setLevel(FijkLogLevel.Warn); _instance = FijkPlayer(); // FFmpeg options @@ -81,7 +91,56 @@ class IjkPlayerAvesVideoController extends AvesVideoController { _instance.release(); } - void _onValueChanged() => _valueStreamController.add(_instance.value); + void _fetchSelectedStreams() async { + final mediaInfo = await _instance.getInfo(); + if (!mediaInfo.containsKey(Keys.streams)) return; + + _streams.clear(); + final allStreams = (mediaInfo[Keys.streams] as List).cast(); + allStreams.forEach((stream) { + final type = ExtraStreamType.fromTypeString(stream[Keys.streamType]); + if (type != null) { + _streams.add(StreamSummary( + type: type, + index: stream[Keys.index], + language: stream[Keys.language], + title: stream[Keys.title], + )); + } + }); + + StreamSummary _getSelectedStream(String selectedIndexKey) { + final indexString = mediaInfo[selectedIndexKey]; + if (indexString != null) { + final index = int.tryParse(indexString); + if (index != null && index != -1) { + return _streams.firstWhere((stream) => stream.index == index, orElse: () => null); + } + } + return null; + } + + _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 streamInfo = allStreams.firstWhere((stream) => stream[Keys.index] == streamIndex, orElse: () => null); + if (streamInfo != null) { + final num = streamInfo[Keys.sarNum]; + final den = streamInfo[Keys.sarDen]; + _sar.value = Tuple2((num ?? 0) != 0 ? num : 1, (den ?? 0) != 0 ? den : 1); + } + } + } + + void _onValueChanged() { + if (_instance.state == FijkState.prepared && _streams.isEmpty) { + _fetchSelectedStreams(); + } + _valueStreamController.add(_instance.value); + } // enable autoplay, even when seeking on uninitialized player, otherwise the texture is not updated // as a workaround, pausing after a brief duration is possible, but fiddly @@ -126,19 +185,27 @@ class IjkPlayerAvesVideoController extends AvesVideoController { @override Widget buildPlayerWidget(BuildContext context, AvesEntry entry) { - // TODO TLAD derive DAR (Display Aspect Ratio) from SAR (Storage Aspect Ratio), if any - // e.g. 960x536 (~16:9) with SAR 4:3 should be displayed as ~2.39:1 - return FijkView( - player: _instance, - fit: FijkFit( - sizeFactor: 1.0, - aspectRatio: -1, - alignment: Alignment.topLeft, - macroBlockCrop: _macroBlockCrop, - ), - panelBuilder: (player, data, context, viewSize, texturePos) => SizedBox(), - color: Colors.transparent, - ); + return ValueListenableBuilder>( + valueListenable: _sar, + builder: (context, sar, child) { + final sarNum = sar.item1; + final sarDen = sar.item2; + // derive DAR (Display Aspect Ratio) from SAR (Storage Aspect Ratio), if any + // e.g. 960x536 (~16:9) with SAR 4:3 should be displayed as ~2.39:1 + final dar = entry.displayAspectRatio * sarNum / sarDen; + // TODO TLAD notify SAR to make the magnifier and minimap use the rendering DAR instead of entry DAR + return FijkView( + player: _instance, + fit: FijkFit( + sizeFactor: 1.0, + aspectRatio: dar, + alignment: Alignment.topLeft, + macroBlockCrop: _macroBlockCrop, + ), + panelBuilder: (player, data, context, viewSize, texturePos) => SizedBox(), + color: Colors.transparent, + ); + }); } } @@ -165,3 +232,37 @@ extension ExtraIjkStatus on FijkState { return VideoStatus.idle; } } + +enum StreamType { video, audio, text } + +extension ExtraStreamType on StreamType { + static StreamType fromTypeString(String type) { + switch (type) { + case StreamTypes.video: + return StreamType.video; + case StreamTypes.audio: + return StreamType.audio; + case StreamTypes.subtitle: + case StreamTypes.timedText: + return StreamType.text; + default: + return null; + } + } +} + +class StreamSummary { + final StreamType type; + final int index; + final String language, title; + + const StreamSummary({ + @required this.type, + @required this.index, + @required this.language, + @required this.title, + }); + + @override + String toString() => '$runtimeType#${shortHash(this)}{type: type, index: $index, language: $language, title: $title}'; +} diff --git a/lib/widgets/viewer/info/metadata/metadata_section.dart b/lib/widgets/viewer/info/metadata/metadata_section.dart index 6779617f9..1fa504c1e 100644 --- a/lib/widgets/viewer/info/metadata/metadata_section.dart +++ b/lib/widgets/viewer/info/metadata/metadata_section.dart @@ -189,7 +189,7 @@ class _MetadataSectionSliverState extends State with Auto directories.add(MetadataDirectory(MetadataDirectory.mediaDirectory, null, _toSortedTags(formattedMediaTags))); } - if (mediaInfo.containsKey('streams')) { + if (mediaInfo.containsKey(Keys.streams)) { String getTypeText(Map stream) { final type = stream[Keys.streamType] ?? StreamTypes.unknown; switch (type) { @@ -208,7 +208,7 @@ class _MetadataSectionSliverState extends State with Auto } } - final allStreams = (mediaInfo['streams'] as List).cast(); + final allStreams = (mediaInfo[Keys.streams] as List).cast(); final unknownStreams = allStreams.where((stream) => stream[Keys.streamType] == StreamTypes.unknown).toList(); final knownStreams = allStreams.whereNot(unknownStreams.contains); diff --git a/pubspec.lock b/pubspec.lock index a949baef3..c0adf8864 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -211,7 +211,7 @@ packages: description: path: "." ref: aves - resolved-ref: "3c6f4e0d350416932b3a4efcbf1833b7eaf4adc1" + resolved-ref: "8fcf94a57e2a77a79d255f4499e26503ad411769" url: "git://github.com/deckerst/fijkplayer.git" source: git version: "0.8.7"