diff --git a/android/app/libs/fijkplayer-full-release.aar b/android/app/libs/fijkplayer-full-release.aar new file mode 100644 index 000000000..f9aa1a0a7 Binary files /dev/null and b/android/app/libs/fijkplayer-full-release.aar differ diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 793c48019..9bb119024 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -542,6 +542,8 @@ "@settingsSectionVideo": {}, "settingsVideoShowVideos": "Show videos", "@settingsVideoShowVideos": {}, + "settingsVideoEnableHardwareAcceleration": "Enable hardware acceleration", + "@settingsVideoEnableHardwareAcceleration": {}, "settingsSectionPrivacy": "Privacy", "@settingsSectionPrivacy": {}, diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index e047384b5..a3b110136 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -252,6 +252,7 @@ "settingsSectionVideo": "동영상", "settingsVideoShowVideos": "미디어에 동영상 표시", + "settingsVideoEnableHardwareAcceleration": "하드웨어 가속 사용", "settingsSectionPrivacy": "개인정보 보호", "settingsEnableAnalytics": "진단 데이터 보내기", diff --git a/lib/model/settings/settings.dart b/lib/model/settings/settings.dart index 18d59805a..28783e845 100644 --- a/lib/model/settings/settings.dart +++ b/lib/model/settings/settings.dart @@ -49,6 +49,9 @@ class Settings extends ChangeNotifier { static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details'; static const viewerQuickActionsKey = 'viewer_quick_actions'; + // video + static const isVideoHardwareAccelerationEnabledKey = 'video_hwaccel_mediacodec'; + // info static const infoMapStyleKey = 'info_map_style'; static const infoMapZoomKey = 'info_map_zoom'; @@ -223,6 +226,12 @@ class Settings extends ChangeNotifier { set viewerQuickActions(List newValue) => setAndNotify(viewerQuickActionsKey, newValue.map((v) => v.toString()).toList()); + // video + + set isVideoHardwareAccelerationEnabled(bool newValue) => setAndNotify(isVideoHardwareAccelerationEnabledKey, newValue); + + bool get isVideoHardwareAccelerationEnabled => getBoolOrDefault(isVideoHardwareAccelerationEnabledKey, true); + // info EntryMapStyle get infoMapStyle => getEnumOrDefault(infoMapStyleKey, EntryMapStyle.stamenWatercolor, EntryMapStyle.values); diff --git a/lib/widgets/common/video/fijkplayer.dart b/lib/widgets/common/video/fijkplayer.dart index f095c3029..ec722d7a0 100644 --- a/lib/widgets/common/video/fijkplayer.dart +++ b/lib/widgets/common/video/fijkplayer.dart @@ -1,119 +1,147 @@ -// import 'dart:async'; -// -// import 'package:aves/model/entry.dart'; -// import 'package:aves/utils/change_notifier.dart'; -// import 'package:aves/widgets/common/video/video.dart'; -// import 'package:fijkplayer/fijkplayer.dart'; -// import 'package:flutter/material.dart'; -// -// class FijkPlayerAvesVideoController extends AvesVideoController { -// FijkPlayer _instance; -// final List _subscriptions = []; -// final StreamController _valueStreamController = StreamController.broadcast(); -// final AChangeNotifier _playFinishNotifier = AChangeNotifier(); -// -// Stream get _valueStream => _valueStreamController.stream; -// -// FijkPlayerAvesVideoController() { -// _instance = FijkPlayer(); -// _instance.addListener(_onValueChanged); -// _subscriptions.add(_valueStream.where((value) => value.completed).listen((_) => _playFinishNotifier.notifyListeners())); -// } -// -// @override -// void dispose() { -// _instance.removeListener(_onValueChanged); -// _valueStreamController.close(); -// _subscriptions -// ..forEach((sub) => sub.cancel()) -// ..clear(); -// _instance.release(); -// } -// -// void _onValueChanged() => _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 -// @override -// Future setDataSource(String uri) => _instance.setDataSource(uri, autoPlay: true); -// -// @override -// Future refreshVideoInfo() => null; -// -// @override -// Future play() => _instance.start(); -// -// @override -// Future pause() => _instance.pause(); -// -// @override -// Future seekTo(int targetMillis) => _instance.seekTo(targetMillis); -// -// @override -// Future seekToProgress(double progress) => _instance.seekTo((duration * progress).toInt()); -// -// @override -// Listenable get playCompletedListenable => _playFinishNotifier; -// -// @override -// VideoStatus get status => _instance.state.toAves; -// -// @override -// Stream get statusStream => _valueStream.map((value) => value.state.toAves); -// -// @override -// bool get isVideoReady => _instance.value.videoRenderStart; -// -// @override -// Stream get isVideoReadyStream => _valueStream.map((value) => value.videoRenderStart); -// -// // we check whether video info is ready instead of checking for `noDatasource` status, -// // as the controller could also be uninitialized with the `pause` status -// // (e.g. when switching between video entries without playing them the first time) -// @override -// bool get isPlayable => _instance.isPlayable(); -// -// @override -// int get duration => _instance.value.duration.inMilliseconds; -// -// @override -// int get currentPosition => _instance.currentPos.inMilliseconds; -// -// @override -// Stream get positionStream => _instance.onCurrentPosUpdate.map((pos) => pos.inMilliseconds); -// -// @override -// Widget buildPlayerWidget(AvesEntry entry) => FijkView( -// player: _instance, -// panelBuilder: (player, data, context, viewSize, texturePos) => SizedBox(), -// color: Colors.transparent, -// ); -// } -// -// extension ExtraIjkStatus on FijkState { -// VideoStatus get toAves { -// switch (this) { -// case FijkState.idle: -// return VideoStatus.idle; -// case FijkState.initialized: -// return VideoStatus.initialized; -// case FijkState.asyncPreparing: -// return VideoStatus.preparing; -// case FijkState.prepared: -// return VideoStatus.prepared; -// case FijkState.started: -// return VideoStatus.playing; -// case FijkState.paused: -// return VideoStatus.paused; -// case FijkState.completed: -// return VideoStatus.completed; -// case FijkState.stopped: -// return VideoStatus.stopped; -// case FijkState.end: -// return VideoStatus.disposed; -// case FijkState.error: -// return VideoStatus.error; -// } -// return VideoStatus.idle; -// } -// } +import 'dart:async'; + +import 'package:aves/model/entry.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/utils/change_notifier.dart'; +import 'package:aves/widgets/common/video/video.dart'; +import 'package:fijkplayer/fijkplayer.dart'; +import 'package:flutter/material.dart'; + +class IjkPlayerAvesVideoController extends AvesVideoController { + FijkPlayer _instance; + final List _subscriptions = []; + final StreamController _valueStreamController = StreamController.broadcast(); + final AChangeNotifier _playFinishNotifier = AChangeNotifier(); + + Stream get _valueStream => _valueStreamController.stream; + + IjkPlayerAvesVideoController() { + _instance = FijkPlayer(); + + // FFmpeg options + // cf https://github.com/Bilibili/ijkplayer/blob/master/ijkmedia/ijkplayer/ff_ffplay_options.h + // cf https://www.jianshu.com/p/843c86a9e9ad + + final option = FijkOption(); + // `fastseek`: enable fast, but inaccurate seeks for some formats + option.setFormatOption('fflags', 'fastseek'); + // `enable-accurate-seek`: enable accurate seek, default: 0, in [0, 1] + option.setPlayerOption('enable-accurate-seek', 1); + // `framedrop`: drop frames when cpu is too slow, default: 0, in [-1, 120] + option.setPlayerOption('framedrop', 5); + + final hwAccel = settings.isVideoHardwareAccelerationEnabled ? 1 : 0; + // `mediacodec-all-videos`: MediaCodec: enable all videos, default: 0, in [0, 1] + // TODO TLAD enabling `mediacodec-all-videos` randomly fails to render some videos, e.g. MP2TS/h264(HDPR) + option.setPlayerOption('mediacodec-all-videos', hwAccel); + + // option.setPlayerOption('analyzemaxduration', 200 * 1024); + // option.setPlayerOption('analyzeduration', 200 * 1024); + // option.setPlayerOption('probesize', 1024 * 1024); + + // TODO TLAD check looping + // option.setPlayerOption('loop', 42); + + _instance.applyOptions(option); + + _instance.addListener(_onValueChanged); + _subscriptions.add(_valueStream.where((value) => value.completed).listen((_) => _playFinishNotifier.notifyListeners())); + } + + @override + void dispose() { + _instance.removeListener(_onValueChanged); + _valueStreamController.close(); + _subscriptions + ..forEach((sub) => sub.cancel()) + ..clear(); + _instance.release(); + } + + void _onValueChanged() => _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 + @override + Future setDataSource(String uri) => _instance.setDataSource(uri, autoPlay: true); + + @override + Future refreshVideoInfo() => null; + + @override + Future play() => _instance.start(); + + @override + Future pause() => _instance.pause(); + + @override + Future seekTo(int targetMillis) => _instance.seekTo(targetMillis); + + @override + Future seekToProgress(double progress) => _instance.seekTo((duration * progress).toInt()); + + @override + Listenable get playCompletedListenable => _playFinishNotifier; + + @override + VideoStatus get status => _instance.state.toAves; + + @override + Stream get statusStream => _valueStream.map((value) => value.state.toAves); + + @override + bool get isVideoReady => _instance.value.videoRenderStart; + + @override + Stream get isVideoReadyStream => _valueStream.map((value) => value.videoRenderStart); + + // we check whether video info is ready instead of checking for `noDatasource` status, + // as the controller could also be uninitialized with the `pause` status + // (e.g. when switching between video entries without playing them the first time) + @override + bool get isPlayable => _instance.isPlayable(); + + @override + int get duration => _instance.value.duration.inMilliseconds; + + @override + int get currentPosition => _instance.currentPos.inMilliseconds; + + @override + Stream get positionStream => _instance.onCurrentPosUpdate.map((pos) => pos.inMilliseconds); + + @override + Widget buildPlayerWidget(AvesEntry entry) => FijkView( + player: _instance, + panelBuilder: (player, data, context, viewSize, texturePos) => SizedBox(), + color: Colors.transparent, + ); +} + +extension ExtraIjkStatus on FijkState { + VideoStatus get toAves { + switch (this) { + case FijkState.idle: + return VideoStatus.idle; + case FijkState.initialized: + return VideoStatus.initialized; + case FijkState.asyncPreparing: + return VideoStatus.preparing; + case FijkState.prepared: + return VideoStatus.prepared; + case FijkState.started: + return VideoStatus.playing; + case FijkState.paused: + return VideoStatus.paused; + case FijkState.completed: + return VideoStatus.completed; + case FijkState.stopped: + return VideoStatus.stopped; + case FijkState.end: + return VideoStatus.disposed; + case FijkState.error: + return VideoStatus.error; + } + return VideoStatus.idle; + } +} diff --git a/lib/widgets/common/video/flutter_ijkplayer.dart b/lib/widgets/common/video/flutter_ijkplayer.dart index 4d5f259f6..321f1d55b 100644 --- a/lib/widgets/common/video/flutter_ijkplayer.dart +++ b/lib/widgets/common/video/flutter_ijkplayer.dart @@ -1,144 +1,144 @@ -import 'dart:async'; - -import 'package:aves/model/entry.dart'; -import 'package:aves/utils/change_notifier.dart'; -import 'package:aves/widgets/common/video/video.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_ijkplayer/flutter_ijkplayer.dart'; - -class FlutterIjkPlayerAvesVideoController extends AvesVideoController { - IjkMediaController _instance; - final List _subscriptions = []; - final AChangeNotifier _playFinishNotifier = AChangeNotifier(); - - FlutterIjkPlayerAvesVideoController() { - _instance = IjkMediaController(); - _subscriptions.add(_instance.playFinishStream.listen((_) => _playFinishNotifier.notifyListeners())); - } - - @override - void dispose() { - _subscriptions - ..forEach((sub) => sub.cancel()) - ..clear(); - _instance?.dispose(); - } - - // 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 - @override - Future setDataSource(String uri) => _instance.setDataSource(DataSource.photoManagerUrl(uri), autoPlay: true); - - @override - Future refreshVideoInfo() => _instance.refreshVideoInfo(); - - @override - Future play() => _instance.play(); - - @override - Future pause() => _instance.pause(); - - @override - Future seekTo(int targetMillis) => _instance.seekTo(targetMillis / 1000.0); - - @override - Future seekToProgress(double progress) => _instance.seekToProgress(progress); - - @override - Listenable get playCompletedListenable => _playFinishNotifier; - - @override - VideoStatus get status => _instance.ijkStatus.toAves; - - @override - Stream get statusStream => _instance.ijkStatusStream.map((status) => status.toAves); - - // we check whether video info is ready instead of checking for `noDatasource` status, - // as the controller could also be uninitialized with the `pause` status - // (e.g. when switching between video entries without playing them the first time) - @override - bool get isPlayable => _videoInfo.hasData; - - @override - bool get isVideoReady => _instance.textureId != null; - - @override - Stream get isVideoReadyStream => _instance.textureIdStream.map((id) => id != null); - - // `videoInfo` is never null (even if `toString` prints `null`) - // check presence with `hasData` instead - VideoInfo get _videoInfo => _instance.videoInfo; - - @override - int get duration => _videoInfo.durationMillis; - - @override - int get currentPosition => _videoInfo.currentPositionMillis; - - @override - Stream get positionStream => _instance.videoInfoStream.map((info) => info.currentPositionMillis); - - @override - Widget buildPlayerWidget(AvesEntry entry) => IjkPlayer( - mediaController: _instance, - controllerWidgetBuilder: (controller) => SizedBox.shrink(), - statusWidgetBuilder: (context, controller, status) => SizedBox.shrink(), - textureBuilder: (context, controller, info) { - var id = controller.textureId; - var child = id != null - ? Texture( - textureId: id, - ) - : Container( - color: Colors.black, - ); - - final degree = entry.rotationDegrees ?? 0; - if (degree != 0) { - child = RotatedBox( - quarterTurns: degree ~/ 90, - child: child, - ); - } - - return Center( - child: AspectRatio( - aspectRatio: entry.displayAspectRatio, - child: child, - ), - ); - }, - backgroundColor: Colors.transparent, - ); -} - -extension ExtraVideoInfo on VideoInfo { - int get durationMillis => duration == null ? null : (duration * 1000).toInt(); - - int get currentPositionMillis => currentPosition == null ? null : (currentPosition * 1000).toInt(); -} - -extension ExtraIjkStatus on IjkStatus { - VideoStatus get toAves { - switch (this) { - case IjkStatus.noDatasource: - return VideoStatus.idle; - case IjkStatus.preparing: - return VideoStatus.preparing; - case IjkStatus.prepared: - return VideoStatus.prepared; - case IjkStatus.playing: - return VideoStatus.playing; - case IjkStatus.pause: - return VideoStatus.paused; - case IjkStatus.complete: - return VideoStatus.completed; - case IjkStatus.disposed: - return VideoStatus.disposed; - case IjkStatus.setDatasourceFail: - case IjkStatus.error: - return VideoStatus.error; - } - return VideoStatus.idle; - } -} +// import 'dart:async'; +// +// import 'package:aves/model/entry.dart'; +// import 'package:aves/utils/change_notifier.dart'; +// import 'package:aves/widgets/common/video/video.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_ijkplayer/flutter_ijkplayer.dart'; +// +// class IjkPlayerAvesVideoController extends AvesVideoController { +// IjkMediaController _instance; +// final List _subscriptions = []; +// final AChangeNotifier _playFinishNotifier = AChangeNotifier(); +// +// IjkPlayerAvesVideoController() { +// _instance = IjkMediaController(); +// _subscriptions.add(_instance.playFinishStream.listen((_) => _playFinishNotifier.notifyListeners())); +// } +// +// @override +// void dispose() { +// _subscriptions +// ..forEach((sub) => sub.cancel()) +// ..clear(); +// _instance?.dispose(); +// } +// +// // 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 +// @override +// Future setDataSource(String uri) => _instance.setDataSource(DataSource.photoManagerUrl(uri), autoPlay: true); +// +// @override +// Future refreshVideoInfo() => _instance.refreshVideoInfo(); +// +// @override +// Future play() => _instance.play(); +// +// @override +// Future pause() => _instance.pause(); +// +// @override +// Future seekTo(int targetMillis) => _instance.seekTo(targetMillis / 1000.0); +// +// @override +// Future seekToProgress(double progress) => _instance.seekToProgress(progress); +// +// @override +// Listenable get playCompletedListenable => _playFinishNotifier; +// +// @override +// VideoStatus get status => _instance.ijkStatus.toAves; +// +// @override +// Stream get statusStream => _instance.ijkStatusStream.map((status) => status.toAves); +// +// // we check whether video info is ready instead of checking for `noDatasource` status, +// // as the controller could also be uninitialized with the `pause` status +// // (e.g. when switching between video entries without playing them the first time) +// @override +// bool get isPlayable => _videoInfo.hasData; +// +// @override +// bool get isVideoReady => _instance.textureId != null; +// +// @override +// Stream get isVideoReadyStream => _instance.textureIdStream.map((id) => id != null); +// +// // `videoInfo` is never null (even if `toString` prints `null`) +// // check presence with `hasData` instead +// VideoInfo get _videoInfo => _instance.videoInfo; +// +// @override +// int get duration => _videoInfo.durationMillis; +// +// @override +// int get currentPosition => _videoInfo.currentPositionMillis; +// +// @override +// Stream get positionStream => _instance.videoInfoStream.map((info) => info.currentPositionMillis); +// +// @override +// Widget buildPlayerWidget(AvesEntry entry) => IjkPlayer( +// mediaController: _instance, +// controllerWidgetBuilder: (controller) => SizedBox.shrink(), +// statusWidgetBuilder: (context, controller, status) => SizedBox.shrink(), +// textureBuilder: (context, controller, info) { +// var id = controller.textureId; +// var child = id != null +// ? Texture( +// textureId: id, +// ) +// : Container( +// color: Colors.black, +// ); +// +// final degree = entry.rotationDegrees ?? 0; +// if (degree != 0) { +// child = RotatedBox( +// quarterTurns: degree ~/ 90, +// child: child, +// ); +// } +// +// return Center( +// child: AspectRatio( +// aspectRatio: entry.displayAspectRatio, +// child: child, +// ), +// ); +// }, +// backgroundColor: Colors.transparent, +// ); +// } +// +// extension ExtraVideoInfo on VideoInfo { +// int get durationMillis => duration == null ? null : (duration * 1000).toInt(); +// +// int get currentPositionMillis => currentPosition == null ? null : (currentPosition * 1000).toInt(); +// } +// +// extension ExtraIjkStatus on IjkStatus { +// VideoStatus get toAves { +// switch (this) { +// case IjkStatus.noDatasource: +// return VideoStatus.idle; +// case IjkStatus.preparing: +// return VideoStatus.preparing; +// case IjkStatus.prepared: +// return VideoStatus.prepared; +// case IjkStatus.playing: +// return VideoStatus.playing; +// case IjkStatus.pause: +// return VideoStatus.paused; +// case IjkStatus.complete: +// return VideoStatus.completed; +// case IjkStatus.disposed: +// return VideoStatus.disposed; +// case IjkStatus.setDatasourceFail: +// case IjkStatus.error: +// return VideoStatus.error; +// } +// return VideoStatus.idle; +// } +// } diff --git a/lib/widgets/common/video/video.dart b/lib/widgets/common/video/video.dart index 08d498c37..cdabb17cf 100644 --- a/lib/widgets/common/video/video.dart +++ b/lib/widgets/common/video/video.dart @@ -1,14 +1,12 @@ import 'package:aves/model/entry.dart'; -// import 'package:aves/widgets/common/video/fijkplayer.dart'; -import 'package:aves/widgets/common/video/flutter_ijkplayer.dart'; +import 'package:aves/widgets/common/video/fijkplayer.dart'; +// import 'package:aves/widgets/common/video/flutter_ijkplayer.dart'; import 'package:flutter/material.dart'; abstract class AvesVideoController { AvesVideoController(); - factory AvesVideoController.flutterIjkPlayer() => FlutterIjkPlayerAvesVideoController(); - - // factory AvesVideoController.fijkPlayer() => FijkPlayerAvesVideoController(); + factory AvesVideoController.ijkPlayer() => IjkPlayerAvesVideoController(); void dispose(); diff --git a/lib/widgets/settings/settings_page.dart b/lib/widgets/settings/settings_page.dart index 17ace382b..d041de0ec 100644 --- a/lib/widgets/settings/settings_page.dart +++ b/lib/widgets/settings/settings_page.dart @@ -239,6 +239,11 @@ class _SettingsPageState extends State { onChanged: (v) => context.read().changeFilterVisibility(MimeFilter.video, v), title: Text(context.l10n.settingsVideoShowVideos), ), + SwitchListTile( + value: settings.isVideoHardwareAccelerationEnabled, + onChanged: (v) => settings.isVideoHardwareAccelerationEnabled = v, + title: Text(context.l10n.settingsVideoEnableHardwareAcceleration), + ), ], ); } diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index 876df08e2..57ac87c87 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -499,7 +499,7 @@ class _EntryViewerStackState extends State with SingleTickerPr _initViewSpecificController( uri, _videoControllers, - () => AvesVideoController.flutterIjkPlayer(), + () => AvesVideoController.ijkPlayer(), (_) => _.dispose(), ); } diff --git a/pubspec.lock b/pubspec.lock index d966e4c13..b247f0820 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -206,6 +206,15 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + fijkplayer: + dependency: "direct main" + description: + path: "." + ref: aves + resolved-ref: c48e515a98851e55c857308d5482cd7bf5c9faad + url: "git://github.com/deckerst/fijkplayer.git" + source: git + version: "0.8.7" file: dependency: transitive description: @@ -300,15 +309,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.7.0" - flutter_ijkplayer: - dependency: "direct main" - description: - path: "." - ref: HEAD - resolved-ref: d4e079404ba8e4f82a7e053ffdc47af787a61c3b - url: "git://github.com/deckerst/flutter_ijkplayer.git" - source: git - version: "0.3.7" flutter_image: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7a7db8550..4f7ae6514 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ environment: # not null safe, as of 2021/03/13 # `charts_flutter` - https://github.com/google/charts/issues/579 -# `flutter_ijkplayer` - unmaintained? +# `fijkplayer` - https://github.com/befovy/fijkplayer/issues/381 # `flutter_map` - https://github.com/fleaflet/flutter_map/issues/829 # `latlong` - archived - migrate to maps_toolkit? cf https://github.com/fleaflet/flutter_map/pull/750 # `streams_channel` - unmaintained? - no issue/PR @@ -32,21 +32,19 @@ dependencies: decorated_icon: event_bus: expansion_tile_card: -# path: ../expansion_tile_card git: url: git://github.com/deckerst/expansion_tile_card.git + fijkplayer: + git: + url: git://github.com/deckerst/fijkplayer.git + ref: aves firebase_core: firebase_analytics: firebase_crashlytics: flutter_highlight: -# fijkplayer: -## path: ../fijkplayer +# flutter_ijkplayer: # git: -# url: git://github.com/deckerst/fijkplayer.git -# ref: aves-config - flutter_ijkplayer: - git: - url: git://github.com/deckerst/flutter_ijkplayer.git +# url: git://github.com/deckerst/flutter_ijkplayer.git flutter_localized_locales: flutter_map: flutter_markdown: @@ -125,29 +123,3 @@ flutter: # capture shaders in profile mode (real device only): # % flutter drive -t test_driver/app.dart --profile --cache-sksl --write-sksl-on-exit shaders.sksl.json - -################################################################################ -# Package study - -# brendan-duncan/image (as of v2.1.19): -# - does not support TIFF with JPEG compression (issue #184) -# - TIFF tile decoding is not public (issue #258) - -# video_player (as of v0.10.8+2, backed by ExoPlayer): -# - does not support content URIs (by default, but trivial by fork) -# - does not support AVI/XVID, AC3 -# - cannot play if only the video or audio stream is supported - -# flutter_ijkplayer (as of v0.3.5+1, backed by IJKPlayer & ffmpeg): -# ~ support content URIs (`DataSource.photoManagerUrl` from v0.3.6, but need fork to support content URIs on Android