viewer: play videos in multitrack HEIC
This commit is contained in:
parent
ea51bece7e
commit
1533707aa6
21 changed files with 136 additions and 90 deletions
|
@ -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}';
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,11 +26,15 @@ 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: Provider<MultiPageConductor>(
|
||||||
|
create: (context) => MultiPageConductor(),
|
||||||
|
dispose: (context, value) => value.dispose(),
|
||||||
child: EntryViewerStack(
|
child: EntryViewerStack(
|
||||||
collection: collection,
|
collection: collection,
|
||||||
initialEntry: initialEntry,
|
initialEntry: initialEntry,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
backgroundColor: Navigator.canPop(context) ? Colors.transparent : Colors.black,
|
backgroundColor: Navigator.canPop(context) ? Colors.transparent : Colors.black,
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
),
|
),
|
||||||
|
|
|
@ -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,24 +252,42 @@ 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) {
|
||||||
extraBottomOverlay = VideoControlOverlay(
|
return VideoControlOverlay(
|
||||||
entry: entry,
|
entry: pageEntry,
|
||||||
controller: videoController,
|
controller: videoController,
|
||||||
scale: _bottomOverlayScale,
|
scale: _bottomOverlayScale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (entry.is360) {
|
} else if (pageEntry.is360) {
|
||||||
extraBottomOverlay = PanoramaOverlay(
|
return PanoramaOverlay(
|
||||||
entry: entry,
|
entry: pageEntry,
|
||||||
scale: _bottomOverlayScale,
|
scale: _bottomOverlayScale,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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: [
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
31
lib/widgets/viewer/multipage/conductor.dart
Normal file
31
lib/widgets/viewer/multipage/conductor.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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';
|
|
@ -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';
|
||||||
|
|
|
@ -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 {
|
|
@ -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';
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue