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:
parent
19976940a0
commit
e88568e706
7 changed files with 272 additions and 155 deletions
|
@ -19,10 +19,10 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class FullscreenBody extends StatefulWidget {
|
||||
final CollectionLens collection;
|
||||
|
@ -50,7 +50,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
Animation<Offset> _bottomOverlayOffset;
|
||||
EdgeInsets _frozenViewInsets, _frozenViewPadding;
|
||||
FullscreenActionDelegate _actionDelegate;
|
||||
final List<Tuple2<String, VideoPlayerController>> _videoControllers = [];
|
||||
final List<Tuple2<String, IjkMediaController>> _videoControllers = [];
|
||||
|
||||
CollectionLens get collection => widget.collection;
|
||||
|
||||
|
@ -114,6 +114,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
_overlayAnimationController.dispose();
|
||||
_overlayVisible.removeListener(_onOverlayVisibleChange);
|
||||
_videoControllers.forEach((kv) => kv.item2.dispose());
|
||||
_videoControllers.clear();
|
||||
_verticalPager.removeListener(_onVerticalPageControllerChange);
|
||||
_unregisterWidget(widget);
|
||||
super.dispose();
|
||||
|
@ -330,7 +331,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
|
||||
void _pauseVideoControllers() => _videoControllers.forEach((e) => e.item2.pause());
|
||||
|
||||
void _initVideoController() {
|
||||
Future<void> _initVideoController() async {
|
||||
if (_entry == null || !_entry.isVideo) return;
|
||||
|
||||
final uri = _entry.uri;
|
||||
|
@ -338,9 +339,8 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
if (controllerEntry != null) {
|
||||
_videoControllers.remove(controllerEntry);
|
||||
} else {
|
||||
// unsupported by video_player 0.10.8+2 (backed by ExoPlayer): AVI
|
||||
final controller = VideoPlayerController.uri(uri)..initialize();
|
||||
controllerEntry = Tuple2(uri, controller);
|
||||
// do not set data source of IjkMediaController here
|
||||
controllerEntry = Tuple2(uri, IjkMediaController());
|
||||
}
|
||||
_videoControllers.insert(0, controllerEntry);
|
||||
while (_videoControllers.length > 3) {
|
||||
|
@ -352,7 +352,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
class FullscreenVerticalPageView extends StatefulWidget {
|
||||
final CollectionLens collection;
|
||||
final ImageEntry entry;
|
||||
final List<Tuple2<String, VideoPlayerController>> videoControllers;
|
||||
final List<Tuple2<String, IjkMediaController>> videoControllers;
|
||||
final PageController horizontalPager, verticalPager;
|
||||
final void Function(int page) onVerticalPageChanged, onHorizontalPageChanged;
|
||||
final VoidCallback onImageTap, onImagePageRequested;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/fullscreen/image_view.dart';
|
||||
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class MultiImagePage extends StatefulWidget {
|
||||
final CollectionLens collection;
|
||||
|
@ -12,7 +12,7 @@ class MultiImagePage extends StatefulWidget {
|
|||
final ValueChanged<int> onPageChanged;
|
||||
final ValueChanged<PhotoViewScaleState> onScaleChanged;
|
||||
final VoidCallback onTap;
|
||||
final List<Tuple2<String, VideoPlayerController>> videoControllers;
|
||||
final List<Tuple2<String, IjkMediaController>> videoControllers;
|
||||
|
||||
const MultiImagePage({
|
||||
this.collection,
|
||||
|
@ -65,7 +65,7 @@ class SingleImagePage extends StatefulWidget {
|
|||
final ImageEntry entry;
|
||||
final ValueChanged<PhotoViewScaleState> onScaleChanged;
|
||||
final VoidCallback onTap;
|
||||
final List<Tuple2<String, VideoPlayerController>> videoControllers;
|
||||
final List<Tuple2<String, IjkMediaController>> videoControllers;
|
||||
|
||||
const SingleImagePage({
|
||||
this.entry,
|
||||
|
|
|
@ -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_picture_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/video_view.dart';
|
||||
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class ImageView extends StatelessWidget {
|
||||
final ImageEntry entry;
|
||||
final Object heroTag;
|
||||
final ValueChanged<PhotoViewScaleState> onScaleChanged;
|
||||
final VoidCallback onTap;
|
||||
final List<Tuple2<String, VideoPlayerController>> videoControllers;
|
||||
final List<Tuple2<String, IjkMediaController>> videoControllers;
|
||||
|
||||
const ImageView({
|
||||
this.entry,
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/services/android_app_service.dart';
|
||||
import 'package:aves/utils/time_utils.dart';
|
||||
import 'package:aves/widgets/common/fx/blurred.dart';
|
||||
import 'package:aves/widgets/fullscreen/overlay/common.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
|
||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class VideoControlOverlay extends StatefulWidget {
|
||||
final ImageEntry entry;
|
||||
final Animation<double> scale;
|
||||
final VideoPlayerController controller;
|
||||
final IjkMediaController controller;
|
||||
final EdgeInsets viewInsets, viewPadding;
|
||||
|
||||
const VideoControlOverlay({
|
||||
|
@ -32,16 +34,28 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
final GlobalKey _progressBarKey = GlobalKey();
|
||||
bool _playingOnDragStart = false;
|
||||
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;
|
||||
|
||||
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
|
||||
void initState() {
|
||||
|
@ -51,7 +65,6 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
vsync: this,
|
||||
);
|
||||
_registerWidget(widget);
|
||||
_onValueChange();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -69,11 +82,17 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
}
|
||||
|
||||
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) {
|
||||
widget.controller.removeListener(_onValueChange);
|
||||
_subscriptions
|
||||
..forEach((sub) => sub.cancel())
|
||||
..clear();
|
||||
_stopTimer();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -93,37 +112,43 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
padding: safePadding,
|
||||
child: SizedBox(
|
||||
width: mqWidth - safePadding.horizontal,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: value.hasError
|
||||
? [
|
||||
OverlayButton(
|
||||
scale: scale,
|
||||
child: IconButton(
|
||||
icon: Icon(OMIcons.openInNew),
|
||||
onPressed: () => AndroidAppService.open(entry.uri, entry.mimeTypeAnySubtype),
|
||||
tooltip: 'Open',
|
||||
),
|
||||
),
|
||||
]
|
||||
: [
|
||||
Expanded(
|
||||
child: _buildProgressBar(),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OverlayButton(
|
||||
scale: scale,
|
||||
child: IconButton(
|
||||
icon: AnimatedIcon(
|
||||
icon: AnimatedIcons.play_pause,
|
||||
progress: _playPauseAnimation,
|
||||
),
|
||||
onPressed: _playPause,
|
||||
tooltip: value.isPlaying ? 'Pause' : 'Play',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: StreamBuilder<IjkStatus>(
|
||||
stream: controller.ijkStatusStream,
|
||||
builder: (context, snapshot) {
|
||||
// do not use stream snapshot because it is obsolete when switching between videos
|
||||
final status = controller.ijkStatus;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: status == IjkStatus.error
|
||||
? [
|
||||
OverlayButton(
|
||||
scale: scale,
|
||||
child: IconButton(
|
||||
icon: Icon(OMIcons.openInNew),
|
||||
onPressed: () => AndroidAppService.open(entry.uri, entry.mimeTypeAnySubtype),
|
||||
tooltip: 'Open',
|
||||
),
|
||||
),
|
||||
]
|
||||
: [
|
||||
Expanded(
|
||||
child: _buildProgressBar(),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OverlayButton(
|
||||
scale: scale,
|
||||
child: IconButton(
|
||||
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,
|
||||
child: GestureDetector(
|
||||
onTapDown: (TapDownDetails details) {
|
||||
_seek(details.globalPosition);
|
||||
_seekFromTap(details.globalPosition);
|
||||
},
|
||||
onHorizontalDragStart: (DragStartDetails details) {
|
||||
_playingOnDragStart = controller.value.isPlaying;
|
||||
_playingOnDragStart = isPlaying;
|
||||
if (_playingOnDragStart) controller.pause();
|
||||
},
|
||||
onHorizontalDragUpdate: (DragUpdateDetails details) {
|
||||
_seek(details.globalPosition);
|
||||
_seekFromTap(details.globalPosition);
|
||||
},
|
||||
onHorizontalDragEnd: (DragEndDetails details) {
|
||||
if (_playingOnDragStart) controller.play();
|
||||
|
@ -164,12 +189,25 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
|||
children: [
|
||||
Row(
|
||||
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(),
|
||||
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() {
|
||||
setState(() {});
|
||||
updatePlayPauseIcon();
|
||||
void _startTimer() {
|
||||
if (controller.textureId == null) return;
|
||||
_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 {
|
||||
if (value.isPlaying) {
|
||||
if (isPlaying) {
|
||||
await controller.pause();
|
||||
} else {
|
||||
if (!value.initialized) await controller.initialize();
|
||||
} else if (isInitialized) {
|
||||
await controller.play();
|
||||
} else {
|
||||
await controller.setDataSource(DataSource.photoManagerUrl(entry.uri), autoPlay: true);
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void updatePlayPauseIcon() {
|
||||
final isPlaying = value.isPlaying;
|
||||
void _updatePlayPauseIcon() {
|
||||
final status = _playPauseAnimation.status;
|
||||
if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) {
|
||||
_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 RenderBox box = keyContext.findRenderObject();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
|
||||
|
||||
class AvesVideo extends StatefulWidget {
|
||||
final ImageEntry entry;
|
||||
final VideoPlayerController controller;
|
||||
final IjkMediaController controller;
|
||||
|
||||
const AvesVideo({
|
||||
Key key,
|
||||
|
@ -20,15 +21,16 @@ class AvesVideo extends StatefulWidget {
|
|||
}
|
||||
|
||||
class AvesVideoState extends State<AvesVideo> {
|
||||
final List<StreamSubscription> _subscriptions = [];
|
||||
|
||||
ImageEntry get entry => widget.entry;
|
||||
|
||||
VideoPlayerValue get value => widget.controller.value;
|
||||
IjkMediaController get controller => widget.controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_registerWidget(widget);
|
||||
_onValueChange();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -45,38 +47,78 @@ class AvesVideoState extends State<AvesVideo> {
|
|||
}
|
||||
|
||||
void _registerWidget(AvesVideo widget) {
|
||||
widget.controller.addListener(_onValueChange);
|
||||
_subscriptions.add(widget.controller.playFinishStream.listen(_onPlayFinish));
|
||||
}
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
if (value == null) return const SizedBox();
|
||||
if (value.hasError) {
|
||||
return Image(
|
||||
image: UriImage(uri: entry.uri, mimeType: entry.mimeType),
|
||||
width: entry.width.toDouble(),
|
||||
height: entry.height.toDouble(),
|
||||
);
|
||||
}
|
||||
return Center(
|
||||
child: AspectRatio(
|
||||
aspectRatio: entry.displayAspectRatio,
|
||||
child: VideoPlayer(widget.controller),
|
||||
),
|
||||
);
|
||||
if (controller == null) return const SizedBox();
|
||||
return StreamBuilder<IjkStatus>(
|
||||
stream: widget.controller.ijkStatusStream,
|
||||
builder: (context, snapshot) {
|
||||
final status = snapshot.data;
|
||||
return isPlayable(status)
|
||||
? IjkPlayer(
|
||||
mediaController: controller,
|
||||
controllerWidgetBuilder: (controller) => const SizedBox.shrink(),
|
||||
statusWidgetBuilder: (context, controller, status) => const SizedBox.shrink(),
|
||||
textureBuilder: (context, controller, info) {
|
||||
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() {
|
||||
if (!value.isPlaying && value.position == value.duration) _goToStart();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _goToStart() async {
|
||||
await widget.controller.seekTo(Duration.zero);
|
||||
await widget.controller.pause();
|
||||
}
|
||||
void _onPlayFinish(IjkMediaController controller) => controller.seekTo(0);
|
||||
}
|
||||
|
|
72
pubspec.lock
72
pubspec.lock
|
@ -35,7 +35,7 @@ packages:
|
|||
name: barcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
version: "1.6.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -88,11 +88,9 @@ packages:
|
|||
draggable_scrollbar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "3b823ae0a9def4edec62771f18e6348312bfce15"
|
||||
url: "git://github.com/deckerst/flutter-draggable-scrollbar.git"
|
||||
source: git
|
||||
path: "../flutter-draggable-scrollbar"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.4"
|
||||
event_bus:
|
||||
dependency: "direct main"
|
||||
|
@ -104,26 +102,29 @@ packages:
|
|||
expansion_tile_card:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: edb6b11bb448fc2f30e566a20605b37093503176
|
||||
url: "git://github.com/deckerst/expansion_tile_card.git"
|
||||
source: git
|
||||
path: "../expansion_tile_card"
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.0.3"
|
||||
flushbar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "13c55a8"
|
||||
resolved-ref: "13c55a888c1693f1c8269ea30d55c614a1bfee16"
|
||||
url: "https://github.com/AndreHaueisen/flushbar.git"
|
||||
source: git
|
||||
version: "1.9.1"
|
||||
name: flushbar
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_ijkplayer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../flutter_ijkplayer"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.3.6"
|
||||
flutter_native_timezone:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -144,7 +145,7 @@ packages:
|
|||
name: flutter_svg
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.17.3+1"
|
||||
version: "0.17.4"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@ -168,7 +169,7 @@ packages:
|
|||
name: google_maps_flutter
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.25+2"
|
||||
version: "0.5.26"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -189,7 +190,7 @@ packages:
|
|||
name: io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.3"
|
||||
version: "0.3.4"
|
||||
logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -266,7 +267,7 @@ packages:
|
|||
name: pdf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
version: "1.6.1"
|
||||
pedantic:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -280,14 +281,14 @@ packages:
|
|||
name: percent_indicator
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1+1"
|
||||
version: "2.1.3"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.0+hotfix.2"
|
||||
version: "5.0.0+hotfix.3"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -322,14 +323,14 @@ packages:
|
|||
name: printing
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.3.1"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.4"
|
||||
version: "4.0.5"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -489,27 +490,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
41
pubspec.yaml
41
pubspec.yaml
|
@ -13,6 +13,25 @@ description: A new Flutter application.
|
|||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
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:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
@ -20,19 +39,19 @@ dependencies:
|
|||
charts_flutter:
|
||||
collection:
|
||||
draggable_scrollbar:
|
||||
# path: ../flutter-draggable-scrollbar
|
||||
git:
|
||||
url: git://github.com/deckerst/flutter-draggable-scrollbar.git
|
||||
path: ../flutter-draggable-scrollbar
|
||||
# git:
|
||||
# url: git://github.com/deckerst/flutter-draggable-scrollbar.git
|
||||
event_bus:
|
||||
expansion_tile_card:
|
||||
# path: ../expansion_tile_card
|
||||
git:
|
||||
url: git://github.com/deckerst/expansion_tile_card.git
|
||||
path: ../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-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_svg:
|
||||
geocoder:
|
||||
|
@ -55,8 +74,6 @@ dependencies:
|
|||
transparent_image:
|
||||
tuple:
|
||||
uuid:
|
||||
video_player:
|
||||
path: ../plugins/packages/video_player/video_player
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in a new issue