viewer: play videos in multitrack HEIC

This commit is contained in:
Thibault Deckers 2021-04-22 11:53:46 +09:00
parent ea51bece7e
commit 1533707aa6
21 changed files with 136 additions and 90 deletions

View file

@ -1,3 +1,4 @@
import 'package:aves/ref/mime_types.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
class MultiPageInfo { class MultiPageInfo {
@ -80,6 +81,8 @@ class SinglePageInfo implements Comparable<SinglePageInfo> {
); );
} }
bool get isVideo => MimeTypes.isVideo(mimeType);
@override @override
String toString() => '$runtimeType#${shortHash(this)}{index=$index, pageId=$pageId, mimeType=$mimeType, isDefault=$isDefault, width=$width, height=$height, durationMillis=$durationMillis}'; String toString() => '$runtimeType#${shortHash(this)}{index=$index, pageId=$pageId, mimeType=$mimeType, isDefault=$isDefault, width=$width, height=$height, durationMillis=$durationMillis}';

View file

@ -10,7 +10,7 @@ import 'package:aves/utils/file_utils.dart';
import 'package:aves/utils/math_utils.dart'; import 'package:aves/utils/math_utils.dart';
import 'package:aves/utils/string_utils.dart'; import 'package:aves/utils/string_utils.dart';
import 'package:aves/utils/time_utils.dart'; import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/common/video/fijkplayer.dart'; import 'package:aves/widgets/viewer/video/fijkplayer.dart';
import 'package:fijkplayer/fijkplayer.dart'; import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';

View file

@ -3,24 +3,21 @@ import 'package:aves/model/multipage.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/widgets/common/magnifier/pan/gesture_detector_scope.dart'; import 'package:aves/widgets/common/magnifier/pan/gesture_detector_scope.dart';
import 'package:aves/widgets/common/magnifier/pan/scroll_physics.dart'; import 'package:aves/widgets/common/magnifier/pan/scroll_physics.dart';
import 'package:aves/widgets/viewer/multipage.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart';
import 'package:aves/widgets/viewer/visual/entry_page_view.dart'; import 'package:aves/widgets/viewer/visual/entry_page_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
class MultiEntryScroller extends StatefulWidget { class MultiEntryScroller extends StatefulWidget {
final CollectionLens collection; final CollectionLens collection;
final PageController pageController; final PageController pageController;
final ValueChanged<int> onPageChanged; final ValueChanged<int> onPageChanged;
final List<Tuple2<String, MultiPageController>> multiPageControllers;
final void Function(String uri) onViewDisposed; final void Function(String uri) onViewDisposed;
const MultiEntryScroller({ const MultiEntryScroller({
this.collection, this.collection,
this.pageController, this.pageController,
this.onPageChanged, this.onPageChanged,
this.multiPageControllers,
this.onViewDisposed, this.onViewDisposed,
}); });
@ -48,7 +45,7 @@ class _MultiEntryScrollerState extends State<MultiEntryScroller> with AutomaticK
Widget child; Widget child;
if (entry.isMultipage) { if (entry.isMultipage) {
final multiPageController = _getMultiPageController(entry); final multiPageController = context.read<MultiPageConductor>().getController(entry);
if (multiPageController != null) { if (multiPageController != null) {
child = FutureBuilder<MultiPageInfo>( child = FutureBuilder<MultiPageInfo>(
future: multiPageController.info, future: multiPageController.info,
@ -90,21 +87,15 @@ class _MultiEntryScrollerState extends State<MultiEntryScroller> with AutomaticK
); );
} }
MultiPageController _getMultiPageController(AvesEntry entry) {
return widget.multiPageControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2;
}
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
} }
class SingleEntryScroller extends StatefulWidget { class SingleEntryScroller extends StatefulWidget {
final AvesEntry entry; final AvesEntry entry;
final List<Tuple2<String, MultiPageController>> multiPageControllers;
const SingleEntryScroller({ const SingleEntryScroller({
this.entry, this.entry,
this.multiPageControllers,
}); });
@override @override
@ -120,7 +111,7 @@ class _SingleEntryScrollerState extends State<SingleEntryScroller> with Automati
Widget child; Widget child;
if (entry.isMultipage) { if (entry.isMultipage) {
final multiPageController = _getMultiPageController(entry); final multiPageController = context.read<MultiPageConductor>().getController(entry);
if (multiPageController != null) { if (multiPageController != null) {
child = FutureBuilder<MultiPageInfo>( child = FutureBuilder<MultiPageInfo>(
future: multiPageController.info, future: multiPageController.info,
@ -157,10 +148,6 @@ class _SingleEntryScrollerState extends State<SingleEntryScroller> with Automati
); );
} }
MultiPageController _getMultiPageController(AvesEntry entry) {
return widget.multiPageControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2;
}
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
} }

View file

@ -6,16 +6,13 @@ import 'package:aves/widgets/common/magnifier/pan/scroll_physics.dart';
import 'package:aves/widgets/viewer/entry_horizontal_pager.dart'; import 'package:aves/widgets/viewer/entry_horizontal_pager.dart';
import 'package:aves/widgets/viewer/info/info_page.dart'; import 'package:aves/widgets/viewer/info/info_page.dart';
import 'package:aves/widgets/viewer/info/notifications.dart'; import 'package:aves/widgets/viewer/info/notifications.dart';
import 'package:aves/widgets/viewer/multipage.dart';
import 'package:flutter/foundation.dart'; 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:tuple/tuple.dart';
class ViewerVerticalPageView extends StatefulWidget { class ViewerVerticalPageView extends StatefulWidget {
final CollectionLens collection; final CollectionLens collection;
final ValueNotifier<AvesEntry> entryNotifier; final ValueNotifier<AvesEntry> entryNotifier;
final List<Tuple2<String, MultiPageController>> multiPageControllers;
final PageController horizontalPager, verticalPager; final PageController horizontalPager, verticalPager;
final void Function(int page) onVerticalPageChanged, onHorizontalPageChanged; final void Function(int page) onVerticalPageChanged, onHorizontalPageChanged;
final VoidCallback onImagePageRequested; final VoidCallback onImagePageRequested;
@ -24,7 +21,6 @@ class ViewerVerticalPageView extends StatefulWidget {
const ViewerVerticalPageView({ const ViewerVerticalPageView({
@required this.collection, @required this.collection,
@required this.entryNotifier, @required this.entryNotifier,
@required this.multiPageControllers,
@required this.verticalPager, @required this.verticalPager,
@required this.horizontalPager, @required this.horizontalPager,
@required this.onVerticalPageChanged, @required this.onVerticalPageChanged,
@ -89,12 +85,10 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
collection: collection, collection: collection,
pageController: widget.horizontalPager, pageController: widget.horizontalPager,
onPageChanged: widget.onHorizontalPageChanged, onPageChanged: widget.onHorizontalPageChanged,
multiPageControllers: widget.multiPageControllers,
onViewDisposed: widget.onViewDisposed, onViewDisposed: widget.onViewDisposed,
) )
: SingleEntryScroller( : SingleEntryScroller(
entry: entry, entry: entry,
multiPageControllers: widget.multiPageControllers,
), ),
NotificationListener( NotificationListener(
onNotification: (notification) { onNotification: (notification) {

View file

@ -1,8 +1,9 @@
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/video/conductor.dart';
import 'package:aves/widgets/viewer/entry_viewer_stack.dart'; import 'package:aves/widgets/viewer/entry_viewer_stack.dart';
import 'package:aves/widgets/viewer/multipage/conductor.dart';
import 'package:aves/widgets/viewer/video/conductor.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -25,9 +26,13 @@ class EntryViewerPage extends StatelessWidget {
body: Provider<VideoConductor>( body: Provider<VideoConductor>(
create: (context) => VideoConductor(), create: (context) => VideoConductor(),
dispose: (context, value) => value.dispose(), dispose: (context, value) => value.dispose(),
child: EntryViewerStack( child: Provider<MultiPageConductor>(
collection: collection, create: (context) => MultiPageConductor(),
initialEntry: initialEntry, dispose: (context, value) => value.dispose(),
child: EntryViewerStack(
collection: collection,
initialEntry: initialEntry,
),
), ),
), ),
backgroundColor: Navigator.canPop(context) ? Colors.transparent : Colors.black, backgroundColor: Navigator.canPop(context) ? Colors.transparent : Colors.black,

View file

@ -2,6 +2,7 @@ import 'dart:math';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/multipage.dart';
import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
@ -11,18 +12,18 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/change_notifier.dart';
import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/video/conductor.dart';
import 'package:aves/widgets/common/video/controller.dart';
import 'package:aves/widgets/viewer/entry_action_delegate.dart'; import 'package:aves/widgets/viewer/entry_action_delegate.dart';
import 'package:aves/widgets/viewer/entry_vertical_pager.dart'; import 'package:aves/widgets/viewer/entry_vertical_pager.dart';
import 'package:aves/widgets/viewer/hero.dart'; import 'package:aves/widgets/viewer/hero.dart';
import 'package:aves/widgets/viewer/info/notifications.dart'; import 'package:aves/widgets/viewer/info/notifications.dart';
import 'package:aves/widgets/viewer/multipage.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart'; import 'package:aves/widgets/viewer/overlay/bottom/common.dart';
import 'package:aves/widgets/viewer/overlay/bottom/panorama.dart';
import 'package:aves/widgets/viewer/overlay/bottom/video.dart';
import 'package:aves/widgets/viewer/overlay/notifications.dart'; import 'package:aves/widgets/viewer/overlay/notifications.dart';
import 'package:aves/widgets/viewer/overlay/panorama.dart';
import 'package:aves/widgets/viewer/overlay/top.dart'; import 'package:aves/widgets/viewer/overlay/top.dart';
import 'package:aves/widgets/viewer/overlay/video.dart'; import 'package:aves/widgets/viewer/video/conductor.dart';
import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:aves/widgets/viewer/visual/state.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -57,7 +58,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
Animation<Offset> _bottomOverlayOffset; Animation<Offset> _bottomOverlayOffset;
EdgeInsets _frozenViewInsets, _frozenViewPadding; EdgeInsets _frozenViewInsets, _frozenViewPadding;
EntryActionDelegate _actionDelegate; EntryActionDelegate _actionDelegate;
final List<Tuple2<String, MultiPageController>> _multiPageControllers = [];
final List<Tuple2<String, ValueNotifier<ViewState>>> _viewStateNotifiers = []; final List<Tuple2<String, ValueNotifier<ViewState>>> _viewStateNotifiers = [];
final ValueNotifier<HeroInfo> _heroInfoNotifier = ValueNotifier(null); final ValueNotifier<HeroInfo> _heroInfoNotifier = ValueNotifier(null);
@ -127,8 +127,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
void dispose() { void dispose() {
_overlayAnimationController.dispose(); _overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChange); _overlayVisible.removeListener(_onOverlayVisibleChange);
_multiPageControllers.forEach((kv) => kv.item2.dispose());
_multiPageControllers.clear();
_verticalPager.removeListener(_onVerticalPageControllerChange); _verticalPager.removeListener(_onVerticalPageControllerChange);
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
_unregisterWidget(widget); _unregisterWidget(widget);
@ -195,7 +193,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
ViewerVerticalPageView( ViewerVerticalPageView(
collection: collection, collection: collection,
entryNotifier: _entryNotifier, entryNotifier: _entryNotifier,
multiPageControllers: _multiPageControllers,
verticalPager: _verticalPager, verticalPager: _verticalPager,
horizontalPager: _horizontalPager, horizontalPager: _horizontalPager,
onVerticalPageChanged: _onVerticalPageChanged, onVerticalPageChanged: _onVerticalPageChanged,
@ -225,8 +222,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
builder: (context, entry, child) { builder: (context, entry, child) {
if (entry == null) return SizedBox.shrink(); if (entry == null) return SizedBox.shrink();
final multiPageController = _getMultiPageController(entry);
final viewStateNotifier = _viewStateNotifiers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2; final viewStateNotifier = _viewStateNotifiers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2;
return ViewerTopOverlay( return ViewerTopOverlay(
entry: entry, entry: entry,
@ -236,7 +231,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
viewPadding: _frozenViewPadding, viewPadding: _frozenViewPadding,
onActionSelected: (action) => _actionDelegate.onActionSelected(context, entry, action), onActionSelected: (action) => _actionDelegate.onActionSelected(context, entry, action),
viewStateNotifier: viewStateNotifier, viewStateNotifier: viewStateNotifier,
multiPageController: multiPageController,
); );
}, },
); );
@ -258,25 +252,43 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
builder: (context, entry, child) { builder: (context, entry, child) {
if (entry == null) return SizedBox.shrink(); if (entry == null) return SizedBox.shrink();
final multiPageController = _getMultiPageController(entry); Widget _buildExtraBottomOverlay(AvesEntry pageEntry) {
// a 360 video is both a video and a panorama but only the video controls are displayed
Widget extraBottomOverlay; if (pageEntry.isVideo) {
if (entry.isVideo) { final videoController = context.read<VideoConductor>().getController(pageEntry);
final videoController = context.read<VideoConductor>().getController(entry); if (videoController != null) {
if (videoController != null) { return VideoControlOverlay(
extraBottomOverlay = VideoControlOverlay( entry: pageEntry,
entry: entry, controller: videoController,
controller: videoController, scale: _bottomOverlayScale,
);
}
} else if (pageEntry.is360) {
return PanoramaOverlay(
entry: pageEntry,
scale: _bottomOverlayScale, scale: _bottomOverlayScale,
); );
} }
} else if (entry.is360) { return null;
extraBottomOverlay = PanoramaOverlay(
entry: entry,
scale: _bottomOverlayScale,
);
} }
final multiPageController = entry.isMultipage ? context.read<MultiPageConductor>().getController(entry) : null;
final extraBottomOverlay = multiPageController != null
? FutureBuilder<MultiPageInfo>(
future: multiPageController.info,
builder: (context, snapshot) {
final multiPageInfo = snapshot.data;
if (multiPageInfo == null) return SizedBox.shrink();
return ValueListenableBuilder<int>(
valueListenable: multiPageController.pageNotifier,
builder: (context, page, child) {
final pageEntry = entry.getPageEntry(multiPageInfo?.getByIndex(page));
return _buildExtraBottomOverlay(pageEntry) ?? SizedBox();
},
);
})
: _buildExtraBottomOverlay(entry);
final child = Column( final child = Column(
children: [ children: [
if (extraBottomOverlay != null) if (extraBottomOverlay != null)
@ -331,10 +343,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
return bottomOverlay; return bottomOverlay;
} }
MultiPageController _getMultiPageController(AvesEntry entry) {
return entry.isMultipage ? _multiPageControllers.firstWhere((kv) => kv.item1 == entry.uri, orElse: () => null)?.item2 : null;
}
void _onVerticalPageControllerChange() { void _onVerticalPageControllerChange() {
_verticalScrollNotifier.notifyListeners(); _verticalScrollNotifier.notifyListeners();
} }
@ -504,22 +512,41 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
if (entry.isVideo) { if (entry.isVideo) {
final controller = context.read<VideoConductor>().getOrCreateController(entry); final controller = context.read<VideoConductor>().getOrCreateController(entry);
if (settings.enableVideoAutoPlay) { if (settings.enableVideoAutoPlay) {
_playVideo(controller); _playVideo(controller, () => entry == _entryNotifier.value);
} }
} }
if (entry.isMultipage) { if (entry.isMultipage) {
_initViewSpecificController<MultiPageController>( final multiPageController = context.read<MultiPageConductor>().getOrCreateController(entry);
uri, multiPageController.info.then((info) {
_multiPageControllers, final videoPageEntries = info.pages.where((page) => page.isVideo).map(entry.getPageEntry).toSet();
() => MultiPageController(entry), if (videoPageEntries.isNotEmpty) {
(_) => _.dispose(), // init video controllers for all pages that could need it
); final videoConductor = context.read<VideoConductor>();
videoPageEntries.forEach(videoConductor.getOrCreateController);
// auto play/pause when changing page
void _onPageChange() {
_pauseVideoControllers();
if (settings.enableVideoAutoPlay) {
final page = multiPageController.page;
final pageInfo = info.getByIndex(page);
if (pageInfo.isVideo) {
final pageVideoController = videoConductor.getController(entry.getPageEntry(pageInfo));
_playVideo(pageVideoController, () => entry == _entryNotifier.value && page == multiPageController.page);
}
}
}
multiPageController.pageNotifier.addListener(_onPageChange);
_onPageChange();
}
});
} }
setState(() {}); setState(() {});
} }
Future<void> _playVideo(AvesVideoController videoController) async { Future<void> _playVideo(AvesVideoController videoController, bool Function() isCurrent) async {
// video decoding may fail or have initial artifacts when the player initializes // video decoding may fail or have initial artifacts when the player initializes
// during this widget initialization (because of the page transition and hero animation?) // during this widget initialization (because of the page transition and hero animation?)
// so we play after a delay for increased stability // so we play after a delay for increased stability
@ -530,7 +557,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with SingleTickerPr
// playing controllers are paused when the entry changes, // playing controllers are paused when the entry changes,
// but the controller may still be preparing (not yet playing) when this happens // but the controller may still be preparing (not yet playing) when this happens
// so we make sure the current entry is still the same to keep playing // so we make sure the current entry is still the same to keep playing
if (videoController.entry != _entryNotifier.value) { if (!isCurrent()) {
await videoController.pause(); await videoController.pause();
} }
} }

View file

@ -0,0 +1,31 @@
import 'package:aves/model/entry.dart';
import 'package:aves/widgets/viewer/multipage/controller.dart';
class MultiPageConductor {
final List<MultiPageController> _controllers = [];
static const maxControllerCount = 3;
Future<void> dispose() async {
await Future.forEach(_controllers, (controller) => controller.dispose());
_controllers.clear();
}
MultiPageController getOrCreateController(AvesEntry entry) {
var controller = getController(entry);
if (controller != null) {
_controllers.remove(controller);
} else {
controller = MultiPageController(entry);
}
_controllers.insert(0, controller);
while (_controllers.length > maxControllerCount) {
_controllers.removeLast().dispose();
}
return controller;
}
MultiPageController getController(AvesEntry entry) {
return _controllers.firstWhere((c) => c.entry.uri == entry.uri && c.entry.pageId == entry.pageId, orElse: () => null);
}
}

View file

@ -7,10 +7,11 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class MultiPageController extends ChangeNotifier { class MultiPageController extends ChangeNotifier {
final AvesEntry entry;
Future<MultiPageInfo> info; Future<MultiPageInfo> info;
final ValueNotifier<int> pageNotifier = ValueNotifier(null); final ValueNotifier<int> pageNotifier = ValueNotifier(null);
MultiPageController(AvesEntry entry) { MultiPageController(this.entry) {
info = metadataService.getMultiPageInfo(entry).then((value) { info = metadataService.getMultiPageInfo(entry).then((value) {
pageNotifier.value = value.defaultPage.index; pageNotifier.value = value.defaultPage.index;
return value; return value;

View file

@ -11,9 +11,9 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/viewer/multipage.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:aves/widgets/viewer/overlay/bottom/multipage.dart';
import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves/widgets/viewer/overlay/common.dart';
import 'package:aves/widgets/viewer/overlay/multipage.dart';
import 'package:decorated_icon/decorated_icon.dart'; import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';

View file

@ -5,7 +5,7 @@ import 'package:aves/model/multipage.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/collection/thumbnail/decorated.dart'; import 'package:aves/widgets/collection/thumbnail/decorated.dart';
import 'package:aves/widgets/collection/thumbnail/theme.dart'; import 'package:aves/widgets/collection/thumbnail/theme.dart';
import 'package:aves/widgets/viewer/multipage.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -8,9 +8,9 @@ import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/common/fx/borders.dart'; import 'package:aves/widgets/common/fx/borders.dart';
import 'package:aves/widgets/common/video/controller.dart';
import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves/widgets/viewer/overlay/common.dart';
import 'package:aves/widgets/viewer/overlay/notifications.dart'; import 'package:aves/widgets/viewer/overlay/notifications.dart';
import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class VideoControlOverlay extends StatefulWidget { class VideoControlOverlay extends StatefulWidget {

View file

@ -2,7 +2,7 @@ import 'dart:math';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/multipage.dart'; import 'package:aves/model/multipage.dart';
import 'package:aves/widgets/viewer/multipage.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:aves/widgets/viewer/visual/state.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -7,7 +7,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/menu_row.dart'; import 'package:aves/widgets/common/basic/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/fx/sweeper.dart'; import 'package:aves/widgets/common/fx/sweeper.dart';
import 'package:aves/widgets/viewer/multipage.dart'; import 'package:aves/widgets/viewer/multipage/conductor.dart';
import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves/widgets/viewer/overlay/common.dart';
import 'package:aves/widgets/viewer/overlay/minimap.dart'; import 'package:aves/widgets/viewer/overlay/minimap.dart';
import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:aves/widgets/viewer/visual/state.dart';
@ -23,7 +23,6 @@ class ViewerTopOverlay extends StatelessWidget {
final Function(EntryAction value) onActionSelected; final Function(EntryAction value) onActionSelected;
final bool canToggleFavourite; final bool canToggleFavourite;
final ValueNotifier<ViewState> viewStateNotifier; final ValueNotifier<ViewState> viewStateNotifier;
final MultiPageController multiPageController;
static const double padding = 8; static const double padding = 8;
@ -36,7 +35,6 @@ class ViewerTopOverlay extends StatelessWidget {
@required this.viewPadding, @required this.viewPadding,
@required this.onActionSelected, @required this.onActionSelected,
@required this.viewStateNotifier, @required this.viewStateNotifier,
@required this.multiPageController,
}) : super(key: key); }) : super(key: key);
@override @override
@ -72,7 +70,7 @@ class ViewerTopOverlay extends StatelessWidget {
child: Minimap( child: Minimap(
mainEntry: entry, mainEntry: entry,
viewStateNotifier: viewStateNotifier, viewStateNotifier: viewStateNotifier,
multiPageController: multiPageController, multiPageController: entry.isMultipage ? context.read<MultiPageConductor>().getController(entry) : null,
), ),
) )
], ],

View file

@ -1,6 +1,6 @@
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/widgets/common/video/controller.dart'; import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:aves/widgets/common/video/fijkplayer.dart'; import 'package:aves/widgets/viewer/video/fijkplayer.dart';
import 'package:fijkplayer/fijkplayer.dart'; import 'package:fijkplayer/fijkplayer.dart';
class VideoConductor { class VideoConductor {
@ -31,7 +31,9 @@ class VideoConductor {
return controller; return controller;
} }
AvesVideoController getController(AvesEntry entry) => _controllers.firstWhere((controller) => controller.entry == entry, orElse: () => null); AvesVideoController getController(AvesEntry entry) {
return _controllers.firstWhere((c) => c.entry.uri == entry.uri && c.entry.pageId == entry.pageId, orElse: () => null);
}
void pauseAll() => _controllers.forEach((controller) => controller.pause()); void pauseAll() => _controllers.forEach((controller) => controller.pause());
} }

View file

@ -3,13 +3,11 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
abstract class AvesVideoController { abstract class AvesVideoController {
AvesEntry _entry; final AvesEntry _entry;
AvesEntry get entry => _entry; AvesEntry get entry => _entry;
AvesVideoController(AvesEntry entry) { AvesVideoController(AvesEntry entry) : _entry = entry;
_entry = entry;
}
Future<void> dispose(); Future<void> dispose();

View file

@ -7,7 +7,7 @@ import 'package:aves/model/settings/video_loop_mode.dart';
import 'package:aves/model/video/keys.dart'; import 'package:aves/model/video/keys.dart';
import 'package:aves/model/video/metadata.dart'; import 'package:aves/model/video/metadata.dart';
import 'package:aves/utils/change_notifier.dart'; import 'package:aves/utils/change_notifier.dart';
import 'package:aves/widgets/common/video/controller.dart'; import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:fijkplayer/fijkplayer.dart'; import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -3,7 +3,7 @@
// //
// 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/controller.dart'; // import 'package:aves/widgets/viewer/video/controller.dart';
// import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
// import 'package:flutter/src/foundation/change_notifier.dart'; // import 'package:flutter/src/foundation/change_notifier.dart';
// import 'package:flutter/src/widgets/framework.dart'; // import 'package:flutter/src/widgets/framework.dart';

View file

@ -2,7 +2,7 @@
// //
// 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/controller.dart'; // import 'package:aves/widgets/viewer/video/controller.dart';
// import 'package:flutter/src/foundation/change_notifier.dart'; // import 'package:flutter/src/foundation/change_notifier.dart';
// import 'package:flutter/src/widgets/framework.dart'; // import 'package:flutter/src/widgets/framework.dart';
// import 'package:video_player/video_player.dart'; // import 'package:video_player/video_player.dart';

View file

@ -15,10 +15,10 @@ import 'package:aves/widgets/common/magnifier/magnifier.dart';
import 'package:aves/widgets/common/magnifier/scale/scale_boundaries.dart'; import 'package:aves/widgets/common/magnifier/scale/scale_boundaries.dart';
import 'package:aves/widgets/common/magnifier/scale/scale_level.dart'; import 'package:aves/widgets/common/magnifier/scale/scale_level.dart';
import 'package:aves/widgets/common/magnifier/scale/state.dart'; import 'package:aves/widgets/common/magnifier/scale/state.dart';
import 'package:aves/widgets/common/video/conductor.dart';
import 'package:aves/widgets/common/video/controller.dart';
import 'package:aves/widgets/viewer/hero.dart'; import 'package:aves/widgets/viewer/hero.dart';
import 'package:aves/widgets/viewer/overlay/notifications.dart'; import 'package:aves/widgets/viewer/overlay/notifications.dart';
import 'package:aves/widgets/viewer/video/conductor.dart';
import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:aves/widgets/viewer/visual/error.dart'; import 'package:aves/widgets/viewer/visual/error.dart';
import 'package:aves/widgets/viewer/visual/raster.dart'; import 'package:aves/widgets/viewer/visual/raster.dart';
import 'package:aves/widgets/viewer/visual/state.dart'; import 'package:aves/widgets/viewer/visual/state.dart';

View file

@ -1,5 +1,5 @@
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/widgets/common/video/controller.dart'; import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class VideoView extends StatefulWidget { class VideoView extends StatefulWidget {