video: switched to fijkplayer, optional HW acceleration [WIP]

This commit is contained in:
Thibault Deckers 2021-04-05 19:19:34 +09:00
parent 8d90d6c698
commit 3ddf44b6cc
11 changed files with 328 additions and 313 deletions

Binary file not shown.

View file

@ -542,6 +542,8 @@
"@settingsSectionVideo": {}, "@settingsSectionVideo": {},
"settingsVideoShowVideos": "Show videos", "settingsVideoShowVideos": "Show videos",
"@settingsVideoShowVideos": {}, "@settingsVideoShowVideos": {},
"settingsVideoEnableHardwareAcceleration": "Enable hardware acceleration",
"@settingsVideoEnableHardwareAcceleration": {},
"settingsSectionPrivacy": "Privacy", "settingsSectionPrivacy": "Privacy",
"@settingsSectionPrivacy": {}, "@settingsSectionPrivacy": {},

View file

@ -252,6 +252,7 @@
"settingsSectionVideo": "동영상", "settingsSectionVideo": "동영상",
"settingsVideoShowVideos": "미디어에 동영상 표시", "settingsVideoShowVideos": "미디어에 동영상 표시",
"settingsVideoEnableHardwareAcceleration": "하드웨어 가속 사용",
"settingsSectionPrivacy": "개인정보 보호", "settingsSectionPrivacy": "개인정보 보호",
"settingsEnableAnalytics": "진단 데이터 보내기", "settingsEnableAnalytics": "진단 데이터 보내기",

View file

@ -49,6 +49,9 @@ class Settings extends ChangeNotifier {
static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details'; static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details';
static const viewerQuickActionsKey = 'viewer_quick_actions'; static const viewerQuickActionsKey = 'viewer_quick_actions';
// video
static const isVideoHardwareAccelerationEnabledKey = 'video_hwaccel_mediacodec';
// info // info
static const infoMapStyleKey = 'info_map_style'; static const infoMapStyleKey = 'info_map_style';
static const infoMapZoomKey = 'info_map_zoom'; static const infoMapZoomKey = 'info_map_zoom';
@ -223,6 +226,12 @@ class Settings extends ChangeNotifier {
set viewerQuickActions(List<EntryAction> newValue) => setAndNotify(viewerQuickActionsKey, newValue.map((v) => v.toString()).toList()); set viewerQuickActions(List<EntryAction> newValue) => setAndNotify(viewerQuickActionsKey, newValue.map((v) => v.toString()).toList());
// video
set isVideoHardwareAccelerationEnabled(bool newValue) => setAndNotify(isVideoHardwareAccelerationEnabledKey, newValue);
bool get isVideoHardwareAccelerationEnabled => getBoolOrDefault(isVideoHardwareAccelerationEnabledKey, true);
// info // info
EntryMapStyle get infoMapStyle => getEnumOrDefault(infoMapStyleKey, EntryMapStyle.stamenWatercolor, EntryMapStyle.values); EntryMapStyle get infoMapStyle => getEnumOrDefault(infoMapStyleKey, EntryMapStyle.stamenWatercolor, EntryMapStyle.values);

View file

@ -1,119 +1,147 @@
// import 'dart:async'; import 'dart:async';
//
// import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
// import 'package:aves/utils/change_notifier.dart'; import 'package:aves/model/settings/settings.dart';
// import 'package:aves/widgets/common/video/video.dart'; import 'package:aves/utils/change_notifier.dart';
// import 'package:fijkplayer/fijkplayer.dart'; import 'package:aves/widgets/common/video/video.dart';
// import 'package:flutter/material.dart'; import 'package:fijkplayer/fijkplayer.dart';
// import 'package:flutter/material.dart';
// class FijkPlayerAvesVideoController extends AvesVideoController {
// FijkPlayer _instance; class IjkPlayerAvesVideoController extends AvesVideoController {
// final List<StreamSubscription> _subscriptions = []; FijkPlayer _instance;
// final StreamController<FijkValue> _valueStreamController = StreamController.broadcast(); final List<StreamSubscription> _subscriptions = [];
// final AChangeNotifier _playFinishNotifier = AChangeNotifier(); final StreamController<FijkValue> _valueStreamController = StreamController.broadcast();
// final AChangeNotifier _playFinishNotifier = AChangeNotifier();
// Stream<FijkValue> get _valueStream => _valueStreamController.stream;
// Stream<FijkValue> get _valueStream => _valueStreamController.stream;
// FijkPlayerAvesVideoController() {
// _instance = FijkPlayer(); IjkPlayerAvesVideoController() {
// _instance.addListener(_onValueChanged); _instance = FijkPlayer();
// _subscriptions.add(_valueStream.where((value) => value.completed).listen((_) => _playFinishNotifier.notifyListeners()));
// } // FFmpeg options
// // cf https://github.com/Bilibili/ijkplayer/blob/master/ijkmedia/ijkplayer/ff_ffplay_options.h
// @override // cf https://www.jianshu.com/p/843c86a9e9ad
// void dispose() {
// _instance.removeListener(_onValueChanged); final option = FijkOption();
// _valueStreamController.close(); // `fastseek`: enable fast, but inaccurate seeks for some formats
// _subscriptions option.setFormatOption('fflags', 'fastseek');
// ..forEach((sub) => sub.cancel()) // `enable-accurate-seek`: enable accurate seek, default: 0, in [0, 1]
// ..clear(); option.setPlayerOption('enable-accurate-seek', 1);
// _instance.release(); // `framedrop`: drop frames when cpu is too slow, default: 0, in [-1, 120]
// } option.setPlayerOption('framedrop', 5);
//
// void _onValueChanged() => _valueStreamController.add(_instance.value); final hwAccel = settings.isVideoHardwareAccelerationEnabled ? 1 : 0;
// // `mediacodec-all-videos`: MediaCodec: enable all videos, default: 0, in [0, 1]
// // enable autoplay, even when seeking on uninitialized player, otherwise the texture is not updated // TODO TLAD enabling `mediacodec-all-videos` randomly fails to render some videos, e.g. MP2TS/h264(HDPR)
// // as a workaround, pausing after a brief duration is possible, but fiddly option.setPlayerOption('mediacodec-all-videos', hwAccel);
// @override
// Future<void> setDataSource(String uri) => _instance.setDataSource(uri, autoPlay: true); // option.setPlayerOption('analyzemaxduration', 200 * 1024);
// // option.setPlayerOption('analyzeduration', 200 * 1024);
// @override // option.setPlayerOption('probesize', 1024 * 1024);
// Future<void> refreshVideoInfo() => null;
// // TODO TLAD check looping
// @override // option.setPlayerOption('loop', 42);
// Future<void> play() => _instance.start();
// _instance.applyOptions(option);
// @override
// Future<void> pause() => _instance.pause(); _instance.addListener(_onValueChanged);
// _subscriptions.add(_valueStream.where((value) => value.completed).listen((_) => _playFinishNotifier.notifyListeners()));
// @override }
// Future<void> seekTo(int targetMillis) => _instance.seekTo(targetMillis);
// @override
// @override void dispose() {
// Future<void> seekToProgress(double progress) => _instance.seekTo((duration * progress).toInt()); _instance.removeListener(_onValueChanged);
// _valueStreamController.close();
// @override _subscriptions
// Listenable get playCompletedListenable => _playFinishNotifier; ..forEach((sub) => sub.cancel())
// ..clear();
// @override _instance.release();
// VideoStatus get status => _instance.state.toAves; }
//
// @override void _onValueChanged() => _valueStreamController.add(_instance.value);
// Stream<VideoStatus> get statusStream => _valueStream.map((value) => value.state.toAves);
// // enable autoplay, even when seeking on uninitialized player, otherwise the texture is not updated
// @override // as a workaround, pausing after a brief duration is possible, but fiddly
// bool get isVideoReady => _instance.value.videoRenderStart; @override
// Future<void> setDataSource(String uri) => _instance.setDataSource(uri, autoPlay: true);
// @override
// Stream<bool> get isVideoReadyStream => _valueStream.map((value) => value.videoRenderStart); @override
// Future<void> refreshVideoInfo() => null;
// // we check whether video info is ready instead of checking for `noDatasource` status,
// // as the controller could also be uninitialized with the `pause` status @override
// // (e.g. when switching between video entries without playing them the first time) Future<void> play() => _instance.start();
// @override
// bool get isPlayable => _instance.isPlayable(); @override
// Future<void> pause() => _instance.pause();
// @override
// int get duration => _instance.value.duration.inMilliseconds; @override
// Future<void> seekTo(int targetMillis) => _instance.seekTo(targetMillis);
// @override
// int get currentPosition => _instance.currentPos.inMilliseconds; @override
// Future<void> seekToProgress(double progress) => _instance.seekTo((duration * progress).toInt());
// @override
// Stream<int> get positionStream => _instance.onCurrentPosUpdate.map((pos) => pos.inMilliseconds); @override
// Listenable get playCompletedListenable => _playFinishNotifier;
// @override
// Widget buildPlayerWidget(AvesEntry entry) => FijkView( @override
// player: _instance, VideoStatus get status => _instance.state.toAves;
// panelBuilder: (player, data, context, viewSize, texturePos) => SizedBox(),
// color: Colors.transparent, @override
// ); Stream<VideoStatus> get statusStream => _valueStream.map((value) => value.state.toAves);
// }
// @override
// extension ExtraIjkStatus on FijkState { bool get isVideoReady => _instance.value.videoRenderStart;
// VideoStatus get toAves {
// switch (this) { @override
// case FijkState.idle: Stream<bool> get isVideoReadyStream => _valueStream.map((value) => value.videoRenderStart);
// return VideoStatus.idle;
// case FijkState.initialized: // we check whether video info is ready instead of checking for `noDatasource` status,
// return VideoStatus.initialized; // as the controller could also be uninitialized with the `pause` status
// case FijkState.asyncPreparing: // (e.g. when switching between video entries without playing them the first time)
// return VideoStatus.preparing; @override
// case FijkState.prepared: bool get isPlayable => _instance.isPlayable();
// return VideoStatus.prepared;
// case FijkState.started: @override
// return VideoStatus.playing; int get duration => _instance.value.duration.inMilliseconds;
// case FijkState.paused:
// return VideoStatus.paused; @override
// case FijkState.completed: int get currentPosition => _instance.currentPos.inMilliseconds;
// return VideoStatus.completed;
// case FijkState.stopped: @override
// return VideoStatus.stopped; Stream<int> get positionStream => _instance.onCurrentPosUpdate.map((pos) => pos.inMilliseconds);
// case FijkState.end:
// return VideoStatus.disposed; @override
// case FijkState.error: Widget buildPlayerWidget(AvesEntry entry) => FijkView(
// return VideoStatus.error; player: _instance,
// } panelBuilder: (player, data, context, viewSize, texturePos) => SizedBox(),
// return VideoStatus.idle; 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;
}
}

View file

@ -1,144 +1,144 @@
import 'dart:async'; // import 'dart:async';
//
import 'package:aves/model/entry.dart'; // import 'package:aves/model/entry.dart';
import 'package:aves/utils/change_notifier.dart'; // import 'package:aves/utils/change_notifier.dart';
import 'package:aves/widgets/common/video/video.dart'; // import 'package:aves/widgets/common/video/video.dart';
import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart'; // import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
//
class FlutterIjkPlayerAvesVideoController extends AvesVideoController { // class IjkPlayerAvesVideoController extends AvesVideoController {
IjkMediaController _instance; // IjkMediaController _instance;
final List<StreamSubscription> _subscriptions = []; // final List<StreamSubscription> _subscriptions = [];
final AChangeNotifier _playFinishNotifier = AChangeNotifier(); // final AChangeNotifier _playFinishNotifier = AChangeNotifier();
//
FlutterIjkPlayerAvesVideoController() { // IjkPlayerAvesVideoController() {
_instance = IjkMediaController(); // _instance = IjkMediaController();
_subscriptions.add(_instance.playFinishStream.listen((_) => _playFinishNotifier.notifyListeners())); // _subscriptions.add(_instance.playFinishStream.listen((_) => _playFinishNotifier.notifyListeners()));
} // }
//
@override // @override
void dispose() { // void dispose() {
_subscriptions // _subscriptions
..forEach((sub) => sub.cancel()) // ..forEach((sub) => sub.cancel())
..clear(); // ..clear();
_instance?.dispose(); // _instance?.dispose();
} // }
//
// enable autoplay, even when seeking on uninitialized player, otherwise the texture is not updated // // 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 // // as a workaround, pausing after a brief duration is possible, but fiddly
@override // @override
Future<void> setDataSource(String uri) => _instance.setDataSource(DataSource.photoManagerUrl(uri), autoPlay: true); // Future<void> setDataSource(String uri) => _instance.setDataSource(DataSource.photoManagerUrl(uri), autoPlay: true);
//
@override // @override
Future<void> refreshVideoInfo() => _instance.refreshVideoInfo(); // Future<void> refreshVideoInfo() => _instance.refreshVideoInfo();
//
@override // @override
Future<void> play() => _instance.play(); // Future<void> play() => _instance.play();
//
@override // @override
Future<void> pause() => _instance.pause(); // Future<void> pause() => _instance.pause();
//
@override // @override
Future<void> seekTo(int targetMillis) => _instance.seekTo(targetMillis / 1000.0); // Future<void> seekTo(int targetMillis) => _instance.seekTo(targetMillis / 1000.0);
//
@override // @override
Future<void> seekToProgress(double progress) => _instance.seekToProgress(progress); // Future<void> seekToProgress(double progress) => _instance.seekToProgress(progress);
//
@override // @override
Listenable get playCompletedListenable => _playFinishNotifier; // Listenable get playCompletedListenable => _playFinishNotifier;
//
@override // @override
VideoStatus get status => _instance.ijkStatus.toAves; // VideoStatus get status => _instance.ijkStatus.toAves;
//
@override // @override
Stream<VideoStatus> get statusStream => _instance.ijkStatusStream.map((status) => status.toAves); // Stream<VideoStatus> get statusStream => _instance.ijkStatusStream.map((status) => status.toAves);
//
// we check whether video info is ready instead of checking for `noDatasource` status, // // we check whether video info is ready instead of checking for `noDatasource` status,
// as the controller could also be uninitialized with the `pause` 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) // // (e.g. when switching between video entries without playing them the first time)
@override // @override
bool get isPlayable => _videoInfo.hasData; // bool get isPlayable => _videoInfo.hasData;
//
@override // @override
bool get isVideoReady => _instance.textureId != null; // bool get isVideoReady => _instance.textureId != null;
//
@override // @override
Stream<bool> get isVideoReadyStream => _instance.textureIdStream.map((id) => id != null); // Stream<bool> get isVideoReadyStream => _instance.textureIdStream.map((id) => id != null);
//
// `videoInfo` is never null (even if `toString` prints `null`) // // `videoInfo` is never null (even if `toString` prints `null`)
// check presence with `hasData` instead // // check presence with `hasData` instead
VideoInfo get _videoInfo => _instance.videoInfo; // VideoInfo get _videoInfo => _instance.videoInfo;
//
@override // @override
int get duration => _videoInfo.durationMillis; // int get duration => _videoInfo.durationMillis;
//
@override // @override
int get currentPosition => _videoInfo.currentPositionMillis; // int get currentPosition => _videoInfo.currentPositionMillis;
//
@override // @override
Stream<int> get positionStream => _instance.videoInfoStream.map((info) => info.currentPositionMillis); // Stream<int> get positionStream => _instance.videoInfoStream.map((info) => info.currentPositionMillis);
//
@override // @override
Widget buildPlayerWidget(AvesEntry entry) => IjkPlayer( // Widget buildPlayerWidget(AvesEntry entry) => IjkPlayer(
mediaController: _instance, // mediaController: _instance,
controllerWidgetBuilder: (controller) => SizedBox.shrink(), // controllerWidgetBuilder: (controller) => SizedBox.shrink(),
statusWidgetBuilder: (context, controller, status) => SizedBox.shrink(), // statusWidgetBuilder: (context, controller, status) => SizedBox.shrink(),
textureBuilder: (context, controller, info) { // textureBuilder: (context, controller, info) {
var id = controller.textureId; // var id = controller.textureId;
var child = id != null // var child = id != null
? Texture( // ? Texture(
textureId: id, // textureId: id,
) // )
: Container( // : Container(
color: Colors.black, // color: Colors.black,
); // );
//
final degree = entry.rotationDegrees ?? 0; // final degree = entry.rotationDegrees ?? 0;
if (degree != 0) { // if (degree != 0) {
child = RotatedBox( // child = RotatedBox(
quarterTurns: degree ~/ 90, // quarterTurns: degree ~/ 90,
child: child, // child: child,
); // );
} // }
//
return Center( // return Center(
child: AspectRatio( // child: AspectRatio(
aspectRatio: entry.displayAspectRatio, // aspectRatio: entry.displayAspectRatio,
child: child, // child: child,
), // ),
); // );
}, // },
backgroundColor: Colors.transparent, // backgroundColor: Colors.transparent,
); // );
} // }
//
extension ExtraVideoInfo on VideoInfo { // extension ExtraVideoInfo on VideoInfo {
int get durationMillis => duration == null ? null : (duration * 1000).toInt(); // int get durationMillis => duration == null ? null : (duration * 1000).toInt();
//
int get currentPositionMillis => currentPosition == null ? null : (currentPosition * 1000).toInt(); // int get currentPositionMillis => currentPosition == null ? null : (currentPosition * 1000).toInt();
} // }
//
extension ExtraIjkStatus on IjkStatus { // extension ExtraIjkStatus on IjkStatus {
VideoStatus get toAves { // VideoStatus get toAves {
switch (this) { // switch (this) {
case IjkStatus.noDatasource: // case IjkStatus.noDatasource:
return VideoStatus.idle; // return VideoStatus.idle;
case IjkStatus.preparing: // case IjkStatus.preparing:
return VideoStatus.preparing; // return VideoStatus.preparing;
case IjkStatus.prepared: // case IjkStatus.prepared:
return VideoStatus.prepared; // return VideoStatus.prepared;
case IjkStatus.playing: // case IjkStatus.playing:
return VideoStatus.playing; // return VideoStatus.playing;
case IjkStatus.pause: // case IjkStatus.pause:
return VideoStatus.paused; // return VideoStatus.paused;
case IjkStatus.complete: // case IjkStatus.complete:
return VideoStatus.completed; // return VideoStatus.completed;
case IjkStatus.disposed: // case IjkStatus.disposed:
return VideoStatus.disposed; // return VideoStatus.disposed;
case IjkStatus.setDatasourceFail: // case IjkStatus.setDatasourceFail:
case IjkStatus.error: // case IjkStatus.error:
return VideoStatus.error; // return VideoStatus.error;
} // }
return VideoStatus.idle; // return VideoStatus.idle;
} // }
} // }

View file

@ -1,14 +1,12 @@
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
// import 'package:aves/widgets/common/video/fijkplayer.dart'; import 'package:aves/widgets/common/video/fijkplayer.dart';
import 'package:aves/widgets/common/video/flutter_ijkplayer.dart'; // import 'package:aves/widgets/common/video/flutter_ijkplayer.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
abstract class AvesVideoController { abstract class AvesVideoController {
AvesVideoController(); AvesVideoController();
factory AvesVideoController.flutterIjkPlayer() => FlutterIjkPlayerAvesVideoController(); factory AvesVideoController.ijkPlayer() => IjkPlayerAvesVideoController();
// factory AvesVideoController.fijkPlayer() => FijkPlayerAvesVideoController();
void dispose(); void dispose();

View file

@ -239,6 +239,11 @@ class _SettingsPageState extends State<SettingsPage> {
onChanged: (v) => context.read<CollectionSource>().changeFilterVisibility(MimeFilter.video, v), onChanged: (v) => context.read<CollectionSource>().changeFilterVisibility(MimeFilter.video, v),
title: Text(context.l10n.settingsVideoShowVideos), title: Text(context.l10n.settingsVideoShowVideos),
), ),
SwitchListTile(
value: settings.isVideoHardwareAccelerationEnabled,
onChanged: (v) => settings.isVideoHardwareAccelerationEnabled = v,
title: Text(context.l10n.settingsVideoEnableHardwareAcceleration),
),
], ],
); );
} }

View file

@ -499,7 +499,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
_initViewSpecificController<AvesVideoController>( _initViewSpecificController<AvesVideoController>(
uri, uri,
_videoControllers, _videoControllers,
() => AvesVideoController.flutterIjkPlayer(), () => AvesVideoController.ijkPlayer(),
(_) => _.dispose(), (_) => _.dispose(),
); );
} }

View file

@ -206,6 +206,15 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" 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: file:
dependency: transitive dependency: transitive
description: description:
@ -300,15 +309,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.7.0" 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: flutter_image:
dependency: transitive dependency: transitive
description: description:

View file

@ -15,7 +15,7 @@ environment:
# not null safe, as of 2021/03/13 # not null safe, as of 2021/03/13
# `charts_flutter` - https://github.com/google/charts/issues/579 # `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 # `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 # `latlong` - archived - migrate to maps_toolkit? cf https://github.com/fleaflet/flutter_map/pull/750
# `streams_channel` - unmaintained? - no issue/PR # `streams_channel` - unmaintained? - no issue/PR
@ -32,21 +32,19 @@ dependencies:
decorated_icon: decorated_icon:
event_bus: event_bus:
expansion_tile_card: expansion_tile_card:
# path: ../expansion_tile_card
git: git:
url: git://github.com/deckerst/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_core:
firebase_analytics: firebase_analytics:
firebase_crashlytics: firebase_crashlytics:
flutter_highlight: flutter_highlight:
# fijkplayer: # flutter_ijkplayer:
## path: ../fijkplayer
# git: # git:
# url: git://github.com/deckerst/fijkplayer.git # url: git://github.com/deckerst/flutter_ijkplayer.git
# ref: aves-config
flutter_ijkplayer:
git:
url: git://github.com/deckerst/flutter_ijkplayer.git
flutter_localized_locales: flutter_localized_locales:
flutter_map: flutter_map:
flutter_markdown: flutter_markdown:
@ -125,29 +123,3 @@ flutter:
# capture shaders in profile mode (real device only): # 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 # % 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 <Q)
# + does not support AC3 (by default, but possible by custom build)
# + can play if only the video or audio stream is supported
# - edge smear on some videos, depending on dimensions (dimension not multiple of 16?)
# - unmaintained
# fijkplayer (as of v0.8.7, backed by IJKPlayer & ffmpeg):
# + support content URIs
# + does not support XVID, AC3 (by default, but possible by custom build)
# + can play if only the video or audio stream is supported
# + no edge smear (with default build)
# - crash when calling `seekTo` for some files, cf https://github.com/befovy/fijkplayer/issues/360