Squashed commit of the following:

commit a80d48e19d05d6b9978cc293d5d3dd460c387d27
Author: Thibault Deckers <thibault.deckers@gmail.com>
Date:   Mon Apr 20 08:34:50 2020 +0900

    video: fixed status check

commit d5af7cecd5c14c47b108456777da170052b7754f
Author: Thibault Deckers <thibault.deckers@gmail.com>
Date:   Sun Apr 19 22:13:58 2020 +0900

    safer seek

commit f84768dd9ac5a70a4489509bd944685298023550
Author: Thibault Deckers <thibault.deckers@gmail.com>
Date:   Sun Apr 19 22:08:06 2020 +0900

    use forked `flutter_ijkplayer` to support content URIs on Android < Q

commit fde82bc213b0058cd990af2c7678f46b20c78bd7
Author: Thibault Deckers <thibault.deckers@gmail.com>
Date:   Sun Apr 19 18:39:18 2020 +0900

    packages upgrade

commit 14414f32203a5caccdb61902ce75b0d83a1a8656
Author: Thibault Deckers <thibault.deckers@gmail.com>
Date:   Sun Apr 19 14:57:38 2020 +0900

    fixes for flutter_ijkplayer

commit 2944d84d9f334bbe54303f7eb3b82a517664e84a
Author: Thibault Deckers <thibault.deckers@gmail.com>
Date:   Fri Apr 17 15:58:29 2020 +0900

    draft for flutter_ijkplayer

commit 0d82956b8e7e1d4500d09805a5d0fd59d2361ed3
Author: Thibault Deckers <thibault.deckers@gmail.com>
Date:   Fri Apr 17 13:00:14 2020 +0900

    switch from video_player to fijkplayer
This commit is contained in:
Thibault Deckers 2020-04-20 08:36:44 +09:00
parent 19976940a0
commit e88568e706
7 changed files with 272 additions and 155 deletions

View file

@ -19,10 +19,10 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:video_player/video_player.dart';
class FullscreenBody extends StatefulWidget { class FullscreenBody extends StatefulWidget {
final CollectionLens collection; final CollectionLens collection;
@ -50,7 +50,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
Animation<Offset> _bottomOverlayOffset; Animation<Offset> _bottomOverlayOffset;
EdgeInsets _frozenViewInsets, _frozenViewPadding; EdgeInsets _frozenViewInsets, _frozenViewPadding;
FullscreenActionDelegate _actionDelegate; FullscreenActionDelegate _actionDelegate;
final List<Tuple2<String, VideoPlayerController>> _videoControllers = []; final List<Tuple2<String, IjkMediaController>> _videoControllers = [];
CollectionLens get collection => widget.collection; CollectionLens get collection => widget.collection;
@ -114,6 +114,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
_overlayAnimationController.dispose(); _overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChange); _overlayVisible.removeListener(_onOverlayVisibleChange);
_videoControllers.forEach((kv) => kv.item2.dispose()); _videoControllers.forEach((kv) => kv.item2.dispose());
_videoControllers.clear();
_verticalPager.removeListener(_onVerticalPageControllerChange); _verticalPager.removeListener(_onVerticalPageControllerChange);
_unregisterWidget(widget); _unregisterWidget(widget);
super.dispose(); super.dispose();
@ -330,7 +331,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
void _pauseVideoControllers() => _videoControllers.forEach((e) => e.item2.pause()); void _pauseVideoControllers() => _videoControllers.forEach((e) => e.item2.pause());
void _initVideoController() { Future<void> _initVideoController() async {
if (_entry == null || !_entry.isVideo) return; if (_entry == null || !_entry.isVideo) return;
final uri = _entry.uri; final uri = _entry.uri;
@ -338,9 +339,8 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
if (controllerEntry != null) { if (controllerEntry != null) {
_videoControllers.remove(controllerEntry); _videoControllers.remove(controllerEntry);
} else { } else {
// unsupported by video_player 0.10.8+2 (backed by ExoPlayer): AVI // do not set data source of IjkMediaController here
final controller = VideoPlayerController.uri(uri)..initialize(); controllerEntry = Tuple2(uri, IjkMediaController());
controllerEntry = Tuple2(uri, controller);
} }
_videoControllers.insert(0, controllerEntry); _videoControllers.insert(0, controllerEntry);
while (_videoControllers.length > 3) { while (_videoControllers.length > 3) {
@ -352,7 +352,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
class FullscreenVerticalPageView extends StatefulWidget { class FullscreenVerticalPageView extends StatefulWidget {
final CollectionLens collection; final CollectionLens collection;
final ImageEntry entry; final ImageEntry entry;
final List<Tuple2<String, VideoPlayerController>> videoControllers; final List<Tuple2<String, IjkMediaController>> videoControllers;
final PageController horizontalPager, verticalPager; final PageController horizontalPager, verticalPager;
final void Function(int page) onVerticalPageChanged, onHorizontalPageChanged; final void Function(int page) onVerticalPageChanged, onHorizontalPageChanged;
final VoidCallback onImageTap, onImagePageRequested; final VoidCallback onImageTap, onImagePageRequested;

View file

@ -1,10 +1,10 @@
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/fullscreen/image_view.dart'; import 'package:aves/widgets/fullscreen/image_view.dart';
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:video_player/video_player.dart';
class MultiImagePage extends StatefulWidget { class MultiImagePage extends StatefulWidget {
final CollectionLens collection; final CollectionLens collection;
@ -12,7 +12,7 @@ class MultiImagePage extends StatefulWidget {
final ValueChanged<int> onPageChanged; final ValueChanged<int> onPageChanged;
final ValueChanged<PhotoViewScaleState> onScaleChanged; final ValueChanged<PhotoViewScaleState> onScaleChanged;
final VoidCallback onTap; final VoidCallback onTap;
final List<Tuple2<String, VideoPlayerController>> videoControllers; final List<Tuple2<String, IjkMediaController>> videoControllers;
const MultiImagePage({ const MultiImagePage({
this.collection, this.collection,
@ -65,7 +65,7 @@ class SingleImagePage extends StatefulWidget {
final ImageEntry entry; final ImageEntry entry;
final ValueChanged<PhotoViewScaleState> onScaleChanged; final ValueChanged<PhotoViewScaleState> onScaleChanged;
final VoidCallback onTap; final VoidCallback onTap;
final List<Tuple2<String, VideoPlayerController>> videoControllers; final List<Tuple2<String, IjkMediaController>> videoControllers;
const SingleImagePage({ const SingleImagePage({
this.entry, this.entry,

View file

@ -4,18 +4,18 @@ import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
import 'package:aves/widgets/fullscreen/video_view.dart'; import 'package:aves/widgets/fullscreen/video_view.dart';
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:video_player/video_player.dart';
class ImageView extends StatelessWidget { class ImageView extends StatelessWidget {
final ImageEntry entry; final ImageEntry entry;
final Object heroTag; final Object heroTag;
final ValueChanged<PhotoViewScaleState> onScaleChanged; final ValueChanged<PhotoViewScaleState> onScaleChanged;
final VoidCallback onTap; final VoidCallback onTap;
final List<Tuple2<String, VideoPlayerController>> videoControllers; final List<Tuple2<String, IjkMediaController>> videoControllers;
const ImageView({ const ImageView({
this.entry, this.entry,

View file

@ -1,18 +1,20 @@
import 'dart:async';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/services/android_app_service.dart'; import 'package:aves/services/android_app_service.dart';
import 'package:aves/utils/time_utils.dart'; import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/fullscreen/overlay/common.dart'; import 'package:aves/widgets/fullscreen/overlay/common.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
import 'package:outline_material_icons/outline_material_icons.dart'; import 'package:outline_material_icons/outline_material_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:video_player/video_player.dart';
class VideoControlOverlay extends StatefulWidget { class VideoControlOverlay extends StatefulWidget {
final ImageEntry entry; final ImageEntry entry;
final Animation<double> scale; final Animation<double> scale;
final VideoPlayerController controller; final IjkMediaController controller;
final EdgeInsets viewInsets, viewPadding; final EdgeInsets viewInsets, viewPadding;
const VideoControlOverlay({ const VideoControlOverlay({
@ -32,16 +34,28 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
final GlobalKey _progressBarKey = GlobalKey(); final GlobalKey _progressBarKey = GlobalKey();
bool _playingOnDragStart = false; bool _playingOnDragStart = false;
AnimationController _playPauseAnimation; AnimationController _playPauseAnimation;
final List<StreamSubscription> _subscriptions = [];
double _seekTargetPercent;
// video info is not refreshed by default, so we use a timer to do so
Timer _progressTimer;
ImageEntry get entry => widget.entry; ImageEntry get entry => widget.entry;
Animation<double> get scale => widget.scale; Animation<double> get scale => widget.scale;
VideoPlayerController get controller => widget.controller; IjkMediaController get controller => widget.controller;
VideoPlayerValue get value => widget.controller.value; // `videoInfo` is never null (even if `toString` prints `null`)
// check presence with `hasData` instead
VideoInfo get videoInfo => controller.videoInfo;
double get progress => value.position != null && value.duration != null ? value.position.inMilliseconds / value.duration.inMilliseconds : 0; // 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)
bool get isInitialized => videoInfo.hasData;
bool get isPlaying => controller.ijkStatus == IjkStatus.playing;
@override @override
void initState() { void initState() {
@ -51,7 +65,6 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
vsync: this, vsync: this,
); );
_registerWidget(widget); _registerWidget(widget);
_onValueChange();
} }
@override @override
@ -69,11 +82,17 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
} }
void _registerWidget(VideoControlOverlay widget) { void _registerWidget(VideoControlOverlay widget) {
widget.controller.addListener(_onValueChange); _subscriptions.add(widget.controller.ijkStatusStream.listen(_onStatusChange));
_subscriptions.add(widget.controller.textureIdStream.listen(_onTextureIdChange));
_onStatusChange(widget.controller.ijkStatus);
_onTextureIdChange(widget.controller.textureId);
} }
void _unregisterWidget(VideoControlOverlay widget) { void _unregisterWidget(VideoControlOverlay widget) {
widget.controller.removeListener(_onValueChange); _subscriptions
..forEach((sub) => sub.cancel())
..clear();
_stopTimer();
} }
@override @override
@ -93,37 +112,43 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
padding: safePadding, padding: safePadding,
child: SizedBox( child: SizedBox(
width: mqWidth - safePadding.horizontal, width: mqWidth - safePadding.horizontal,
child: Row( child: StreamBuilder<IjkStatus>(
mainAxisAlignment: MainAxisAlignment.end, stream: controller.ijkStatusStream,
children: value.hasError builder: (context, snapshot) {
? [ // do not use stream snapshot because it is obsolete when switching between videos
OverlayButton( final status = controller.ijkStatus;
scale: scale, return Row(
child: IconButton( mainAxisAlignment: MainAxisAlignment.end,
icon: Icon(OMIcons.openInNew), children: status == IjkStatus.error
onPressed: () => AndroidAppService.open(entry.uri, entry.mimeTypeAnySubtype), ? [
tooltip: 'Open', OverlayButton(
), scale: scale,
), child: IconButton(
] icon: Icon(OMIcons.openInNew),
: [ onPressed: () => AndroidAppService.open(entry.uri, entry.mimeTypeAnySubtype),
Expanded( tooltip: 'Open',
child: _buildProgressBar(), ),
), ),
const SizedBox(width: 8), ]
OverlayButton( : [
scale: scale, Expanded(
child: IconButton( child: _buildProgressBar(),
icon: AnimatedIcon( ),
icon: AnimatedIcons.play_pause, const SizedBox(width: 8),
progress: _playPauseAnimation, OverlayButton(
), scale: scale,
onPressed: _playPause, child: IconButton(
tooltip: value.isPlaying ? 'Pause' : 'Play', icon: AnimatedIcon(
), icon: AnimatedIcons.play_pause,
), progress: _playPauseAnimation,
], ),
), onPressed: _playPause,
tooltip: isPlaying ? 'Pause' : 'Play',
),
),
],
);
}),
), ),
); );
}, },
@ -138,14 +163,14 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
borderRadius: progressBarBorderRadius, borderRadius: progressBarBorderRadius,
child: GestureDetector( child: GestureDetector(
onTapDown: (TapDownDetails details) { onTapDown: (TapDownDetails details) {
_seek(details.globalPosition); _seekFromTap(details.globalPosition);
}, },
onHorizontalDragStart: (DragStartDetails details) { onHorizontalDragStart: (DragStartDetails details) {
_playingOnDragStart = controller.value.isPlaying; _playingOnDragStart = isPlaying;
if (_playingOnDragStart) controller.pause(); if (_playingOnDragStart) controller.pause();
}, },
onHorizontalDragUpdate: (DragUpdateDetails details) { onHorizontalDragUpdate: (DragUpdateDetails details) {
_seek(details.globalPosition); _seekFromTap(details.globalPosition);
}, },
onHorizontalDragEnd: (DragEndDetails details) { onHorizontalDragEnd: (DragEndDetails details) {
if (_playingOnDragStart) controller.play(); if (_playingOnDragStart) controller.play();
@ -164,12 +189,25 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
children: [ children: [
Row( Row(
children: [ children: [
Text(formatDuration(value.position ?? Duration.zero)), StreamBuilder<VideoInfo>(
stream: controller.videoInfoStream,
builder: (context, snapshot) {
// do not use stream snapshot because it is obsolete when switching between videos
final position = videoInfo.currentPosition?.floor() ?? 0;
return Text(formatDuration(Duration(seconds: position)));
}),
const Spacer(), const Spacer(),
Text(formatDuration(value.duration ?? Duration.zero)), Text(entry.durationText),
], ],
), ),
LinearProgressIndicator(value: progress), StreamBuilder<VideoInfo>(
stream: controller.videoInfoStream,
builder: (context, snapshot) {
// do not use stream snapshot because it is obsolete when switching between videos
var progress = videoInfo.progress;
if (!progress.isFinite) progress = 0.0;
return LinearProgressIndicator(value: progress);
}),
], ],
), ),
), ),
@ -178,23 +216,44 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
); );
} }
void _onValueChange() { void _startTimer() {
setState(() {}); if (controller.textureId == null) return;
updatePlayPauseIcon(); _progressTimer?.cancel();
_progressTimer = Timer.periodic(const Duration(milliseconds: 350), (timer) {
controller.refreshVideoInfo();
});
}
void _stopTimer() {
_progressTimer?.cancel();
}
void _onTextureIdChange(int textureId) {
if (textureId != null) {
_startTimer();
} else {
_stopTimer();
}
}
void _onStatusChange(IjkStatus status) {
if (status == IjkStatus.playing && _seekTargetPercent != null) {
_seekFromTarget();
}
_updatePlayPauseIcon();
} }
Future<void> _playPause() async { Future<void> _playPause() async {
if (value.isPlaying) { if (isPlaying) {
await controller.pause(); await controller.pause();
} else { } else if (isInitialized) {
if (!value.initialized) await controller.initialize();
await controller.play(); await controller.play();
} else {
await controller.setDataSource(DataSource.photoManagerUrl(entry.uri), autoPlay: true);
} }
setState(() {});
} }
void updatePlayPauseIcon() { void _updatePlayPauseIcon() {
final isPlaying = value.isPlaying;
final status = _playPauseAnimation.status; final status = _playPauseAnimation.status;
if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) { if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) {
_playPauseAnimation.forward(); _playPauseAnimation.forward();
@ -203,10 +262,29 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
} }
} }
void _seek(Offset globalPosition) { void _seekFromTap(Offset globalPosition) async {
final keyContext = _progressBarKey.currentContext; final keyContext = _progressBarKey.currentContext;
final RenderBox box = keyContext.findRenderObject(); final RenderBox box = keyContext.findRenderObject();
final localPosition = box.globalToLocal(globalPosition); final localPosition = box.globalToLocal(globalPosition);
controller.seekTo(value.duration * (localPosition.dx / box.size.width)); _seekTargetPercent = (localPosition.dx / box.size.width);
if (isInitialized) {
await _seekFromTarget();
} else {
// autoplay when seeking on uninitialized player, otherwise the texture is not updated
// as a workaround, pausing after a brief duration is possible, but fiddly
await controller.setDataSource(DataSource.photoManagerUrl(entry.uri), autoPlay: true);
}
}
Future _seekFromTarget() async {
// `seekToProgress` is not safe as it can be called when the `duration` is not set yet
// so we make sure the video info is up to date first
if (videoInfo.duration == null) {
await controller.refreshVideoInfo();
} else {
await controller.seekToProgress(_seekTargetPercent);
_seekTargetPercent = null;
}
} }
} }

View file

@ -1,13 +1,14 @@
import 'dart:async';
import 'dart:ui'; import 'dart:ui';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart'; import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
class AvesVideo extends StatefulWidget { class AvesVideo extends StatefulWidget {
final ImageEntry entry; final ImageEntry entry;
final VideoPlayerController controller; final IjkMediaController controller;
const AvesVideo({ const AvesVideo({
Key key, Key key,
@ -20,15 +21,16 @@ class AvesVideo extends StatefulWidget {
} }
class AvesVideoState extends State<AvesVideo> { class AvesVideoState extends State<AvesVideo> {
final List<StreamSubscription> _subscriptions = [];
ImageEntry get entry => widget.entry; ImageEntry get entry => widget.entry;
VideoPlayerValue get value => widget.controller.value; IjkMediaController get controller => widget.controller;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_registerWidget(widget); _registerWidget(widget);
_onValueChange();
} }
@override @override
@ -45,38 +47,78 @@ class AvesVideoState extends State<AvesVideo> {
} }
void _registerWidget(AvesVideo widget) { void _registerWidget(AvesVideo widget) {
widget.controller.addListener(_onValueChange); _subscriptions.add(widget.controller.playFinishStream.listen(_onPlayFinish));
} }
void _unregisterWidget(AvesVideo widget) { void _unregisterWidget(AvesVideo widget) {
widget.controller.removeListener(_onValueChange); _subscriptions
..forEach((sub) => sub.cancel())
..clear();
} }
bool isPlayable(IjkStatus status) => [IjkStatus.prepared, IjkStatus.playing, IjkStatus.pause, IjkStatus.complete].contains(status);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (value == null) return const SizedBox(); if (controller == null) return const SizedBox();
if (value.hasError) { return StreamBuilder<IjkStatus>(
return Image( stream: widget.controller.ijkStatusStream,
image: UriImage(uri: entry.uri, mimeType: entry.mimeType), builder: (context, snapshot) {
width: entry.width.toDouble(), final status = snapshot.data;
height: entry.height.toDouble(), return isPlayable(status)
); ? IjkPlayer(
} mediaController: controller,
return Center( controllerWidgetBuilder: (controller) => const SizedBox.shrink(),
child: AspectRatio( statusWidgetBuilder: (context, controller, status) => const SizedBox.shrink(),
aspectRatio: entry.displayAspectRatio, textureBuilder: (context, controller, info) {
child: VideoPlayer(widget.controller), var id = controller.textureId;
), if (id == null) {
); return AspectRatio(
aspectRatio: entry.displayAspectRatio,
child: Container(
color: Colors.green,
),
);
}
Widget child = Container(
color: Colors.blue,
child: Texture(
textureId: id,
),
);
if (!controller.autoRotate) {
return child;
}
final degree = entry.catalogMetadata?.videoRotation ?? 0;
if (degree != 0) {
child = RotatedBox(
quarterTurns: degree ~/ 90,
child: child,
);
}
child = AspectRatio(
aspectRatio: entry.displayAspectRatio,
child: child,
);
return Container(
child: child,
alignment: Alignment.center,
color: Colors.transparent,
);
},
)
: Image(
image: UriImage(uri: entry.uri, mimeType: entry.mimeType),
width: entry.width.toDouble(),
height: entry.height.toDouble(),
);
});
} }
void _onValueChange() { void _onPlayFinish(IjkMediaController controller) => controller.seekTo(0);
if (!value.isPlaying && value.position == value.duration) _goToStart();
setState(() {});
}
Future<void> _goToStart() async {
await widget.controller.seekTo(Duration.zero);
await widget.controller.pause();
}
} }

View file

@ -35,7 +35,7 @@ packages:
name: barcode name: barcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.0" version: "1.6.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -88,11 +88,9 @@ packages:
draggable_scrollbar: draggable_scrollbar:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "../flutter-draggable-scrollbar"
ref: HEAD relative: true
resolved-ref: "3b823ae0a9def4edec62771f18e6348312bfce15" source: path
url: "git://github.com/deckerst/flutter-draggable-scrollbar.git"
source: git
version: "0.0.4" version: "0.0.4"
event_bus: event_bus:
dependency: "direct main" dependency: "direct main"
@ -104,26 +102,29 @@ packages:
expansion_tile_card: expansion_tile_card:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "../expansion_tile_card"
ref: HEAD relative: true
resolved-ref: edb6b11bb448fc2f30e566a20605b37093503176 source: path
url: "git://github.com/deckerst/expansion_tile_card.git"
source: git
version: "1.0.3" version: "1.0.3"
flushbar: flushbar:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." name: flushbar
ref: "13c55a8" url: "https://pub.dartlang.org"
resolved-ref: "13c55a888c1693f1c8269ea30d55c614a1bfee16" source: hosted
url: "https://github.com/AndreHaueisen/flushbar.git" version: "1.10.0"
source: git
version: "1.9.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_ijkplayer:
dependency: "direct main"
description:
path: "../flutter_ijkplayer"
relative: true
source: path
version: "0.3.6"
flutter_native_timezone: flutter_native_timezone:
dependency: "direct main" dependency: "direct main"
description: description:
@ -144,7 +145,7 @@ packages:
name: flutter_svg name: flutter_svg
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.17.3+1" version: "0.17.4"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -168,7 +169,7 @@ packages:
name: google_maps_flutter name: google_maps_flutter
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.25+2" version: "0.5.26"
image: image:
dependency: transitive dependency: transitive
description: description:
@ -189,7 +190,7 @@ packages:
name: io name: io
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.3" version: "0.3.4"
logger: logger:
dependency: "direct main" dependency: "direct main"
description: description:
@ -266,7 +267,7 @@ packages:
name: pdf name: pdf
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.6.0" version: "1.6.1"
pedantic: pedantic:
dependency: "direct main" dependency: "direct main"
description: description:
@ -280,14 +281,14 @@ packages:
name: percent_indicator name: percent_indicator
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1+1" version: "2.1.3"
permission_handler: permission_handler:
dependency: "direct main" dependency: "direct main"
description: description:
name: permission_handler name: permission_handler
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.0.0+hotfix.2" version: "5.0.0+hotfix.3"
permission_handler_platform_interface: permission_handler_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -322,14 +323,14 @@ packages:
name: printing name: printing
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.2.1" version: "3.3.1"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.4" version: "4.0.5"
qr: qr:
dependency: transitive dependency: transitive
description: description:
@ -489,27 +490,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.8" version: "2.0.8"
video_player:
dependency: "direct main"
description:
path: "../plugins/packages/video_player/video_player"
relative: true
source: path
version: "0.10.8+2"
video_player_platform_interface:
dependency: transitive
description:
name: video_player_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
video_player_web:
dependency: transitive
description:
name: video_player_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2+1"
xml: xml:
dependency: transitive dependency: transitive
description: description:

View file

@ -13,6 +13,25 @@ description: A new Flutter application.
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1 version: 1.0.0+1
# video_player (as of v0.10.8+2, backed by ExoPlayer):
# - no URI handling by default but trivial by fork
# - support: AVI/XVID/MP3 nothing, MP2T nothing
# - cannot support more formats
# - playable only when both the video and audio streams are supported
# fijkplayer (as of v0.7.1, backed by IJKPlayer & ffmpeg):
# - URI handling
# - support: AVI/XVID/MP3 audio only, MP2T video only
# - possible support for more formats by customizing ffmpeg build,
# - playable when only the video or audio stream is supported
# - crash when calling `seekTo` for some files (e.g. TED talk videos)
# flutter_ijkplayer (as of v0.3.5+1, backed by IJKPlayer & ffmpeg):
# - URI handling (from v0.3.6, `DataSource.photoManagerUrl`)
# - support: AVI/XVID/MP3 video/audio, MP2T video only
# - possible support for more formats by TODO TLAD customizing ffmpeg build?
# - playable when only the video or audio stream is supported
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
@ -20,19 +39,19 @@ dependencies:
charts_flutter: charts_flutter:
collection: collection:
draggable_scrollbar: draggable_scrollbar:
# path: ../flutter-draggable-scrollbar path: ../flutter-draggable-scrollbar
git: # git:
url: git://github.com/deckerst/flutter-draggable-scrollbar.git # url: git://github.com/deckerst/flutter-draggable-scrollbar.git
event_bus: event_bus:
expansion_tile_card: expansion_tile_card:
# path: ../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
flutter_ijkplayer:
path: ../flutter_ijkplayer
# git:
# url: git://github.com/deckerst/flutter_ijkplayer.git
flushbar: flushbar:
# flushbar-1.9.1 cannot be built with Flutter 1.15.17
git:
url: https://github.com/AndreHaueisen/flushbar.git
ref: 13c55a8
flutter_native_timezone: flutter_native_timezone:
flutter_svg: flutter_svg:
geocoder: geocoder:
@ -55,8 +74,6 @@ dependencies:
transparent_image: transparent_image:
tuple: tuple:
uuid: uuid:
video_player:
path: ../plugins/packages/video_player/video_player
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: