#962 video: A-B repeat
This commit is contained in:
parent
8555322460
commit
46aef919be
16 changed files with 572 additions and 124 deletions
|
@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
- Collection: support for Fairphone burst pattern
|
- Collection: support for Fairphone burst pattern
|
||||||
- Collection: allow using tags/make/model when bulk renaming
|
- Collection: allow using tags/make/model when bulk renaming
|
||||||
|
- Video: A-B repeat
|
||||||
- Settings: hidden items can be toggled
|
- Settings: hidden items can be toggled
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
"actionRemove": "Remove",
|
"actionRemove": "Remove",
|
||||||
"resetTooltip": "Reset",
|
"resetTooltip": "Reset",
|
||||||
"saveTooltip": "Save",
|
"saveTooltip": "Save",
|
||||||
|
"stopTooltip": "Stop",
|
||||||
"pickTooltip": "Pick",
|
"pickTooltip": "Pick",
|
||||||
|
|
||||||
"doubleBackExitMessage": "Tap “back” again to exit.",
|
"doubleBackExitMessage": "Tap “back” again to exit.",
|
||||||
|
@ -127,6 +128,10 @@
|
||||||
"videoActionSkip10": "Seek forward 10 seconds",
|
"videoActionSkip10": "Seek forward 10 seconds",
|
||||||
"videoActionSelectStreams": "Select tracks",
|
"videoActionSelectStreams": "Select tracks",
|
||||||
"videoActionSetSpeed": "Playback speed",
|
"videoActionSetSpeed": "Playback speed",
|
||||||
|
"videoActionABRepeat": "A-B repeat",
|
||||||
|
|
||||||
|
"videoRepeatActionSetStart": "Set start",
|
||||||
|
"videoRepeatActionSetEnd": "Set end",
|
||||||
|
|
||||||
"viewerActionSettings": "Settings",
|
"viewerActionSettings": "Settings",
|
||||||
"viewerActionLock": "Lock viewer",
|
"viewerActionLock": "Lock viewer",
|
||||||
|
|
|
@ -119,7 +119,10 @@ class AIcons {
|
||||||
static const pause = Icons.pause;
|
static const pause = Icons.pause;
|
||||||
static const print = Icons.print_outlined;
|
static const print = Icons.print_outlined;
|
||||||
static const refresh = Icons.refresh_outlined;
|
static const refresh = Icons.refresh_outlined;
|
||||||
|
static const repeat = Icons.repeat_outlined;
|
||||||
|
static final repeatOff = MdiIcons.repeatOff;
|
||||||
static const replay10 = Icons.replay_10_outlined;
|
static const replay10 = Icons.replay_10_outlined;
|
||||||
|
static final resetBounds = MdiIcons.rayStartEnd;
|
||||||
static const reverse = Icons.invert_colors_outlined;
|
static const reverse = Icons.invert_colors_outlined;
|
||||||
static const skip10 = Icons.forward_10_outlined;
|
static const skip10 = Icons.forward_10_outlined;
|
||||||
static const reset = Icons.restart_alt_outlined;
|
static const reset = Icons.restart_alt_outlined;
|
||||||
|
@ -131,6 +134,8 @@ class AIcons {
|
||||||
static const select = Icons.select_all_outlined;
|
static const select = Icons.select_all_outlined;
|
||||||
static const setAs = Icons.wallpaper_outlined;
|
static const setAs = Icons.wallpaper_outlined;
|
||||||
static final setCover = MdiIcons.imageEditOutline;
|
static final setCover = MdiIcons.imageEditOutline;
|
||||||
|
static final setEnd = MdiIcons.rayEnd;
|
||||||
|
static final setStart = MdiIcons.rayStart;
|
||||||
static const share = Icons.share_outlined;
|
static const share = Icons.share_outlined;
|
||||||
static const show = Icons.visibility_outlined;
|
static const show = Icons.visibility_outlined;
|
||||||
static final showFullscreen = MdiIcons.arrowExpand;
|
static final showFullscreen = MdiIcons.arrowExpand;
|
||||||
|
|
|
@ -36,6 +36,7 @@ extension ExtraEntryActionView on EntryAction {
|
||||||
l10n.videoActionMute,
|
l10n.videoActionMute,
|
||||||
EntryAction.videoSelectStreams => l10n.videoActionSelectStreams,
|
EntryAction.videoSelectStreams => l10n.videoActionSelectStreams,
|
||||||
EntryAction.videoSetSpeed => l10n.videoActionSetSpeed,
|
EntryAction.videoSetSpeed => l10n.videoActionSetSpeed,
|
||||||
|
EntryAction.videoABRepeat => l10n.videoActionABRepeat,
|
||||||
EntryAction.videoSettings => l10n.viewerActionSettings,
|
EntryAction.videoSettings => l10n.viewerActionSettings,
|
||||||
EntryAction.videoTogglePlay =>
|
EntryAction.videoTogglePlay =>
|
||||||
// different data depending on toggle state
|
// different data depending on toggle state
|
||||||
|
@ -110,6 +111,7 @@ extension ExtraEntryActionView on EntryAction {
|
||||||
AIcons.mute,
|
AIcons.mute,
|
||||||
EntryAction.videoSelectStreams => AIcons.streams,
|
EntryAction.videoSelectStreams => AIcons.streams,
|
||||||
EntryAction.videoSetSpeed => AIcons.speed,
|
EntryAction.videoSetSpeed => AIcons.speed,
|
||||||
|
EntryAction.videoABRepeat => AIcons.repeat,
|
||||||
EntryAction.videoSettings => AIcons.videoSettings,
|
EntryAction.videoSettings => AIcons.videoSettings,
|
||||||
EntryAction.videoTogglePlay =>
|
EntryAction.videoTogglePlay =>
|
||||||
// different data depending on toggle state
|
// different data depending on toggle state
|
||||||
|
|
|
@ -94,6 +94,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
return !settings.useTvLayout && targetEntry.isPureVideo;
|
return !settings.useTvLayout && targetEntry.isPureVideo;
|
||||||
case EntryAction.videoSelectStreams:
|
case EntryAction.videoSelectStreams:
|
||||||
case EntryAction.videoSetSpeed:
|
case EntryAction.videoSetSpeed:
|
||||||
|
case EntryAction.videoABRepeat:
|
||||||
case EntryAction.videoSettings:
|
case EntryAction.videoSettings:
|
||||||
case EntryAction.videoTogglePlay:
|
case EntryAction.videoTogglePlay:
|
||||||
case EntryAction.videoReplay10:
|
case EntryAction.videoReplay10:
|
||||||
|
@ -229,6 +230,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
case EntryAction.videoToggleMute:
|
case EntryAction.videoToggleMute:
|
||||||
case EntryAction.videoSelectStreams:
|
case EntryAction.videoSelectStreams:
|
||||||
case EntryAction.videoSetSpeed:
|
case EntryAction.videoSetSpeed:
|
||||||
|
case EntryAction.videoABRepeat:
|
||||||
case EntryAction.videoSettings:
|
case EntryAction.videoSettings:
|
||||||
case EntryAction.videoTogglePlay:
|
case EntryAction.videoTogglePlay:
|
||||||
case EntryAction.videoReplay10:
|
case EntryAction.videoReplay10:
|
||||||
|
|
|
@ -64,6 +64,8 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
await _showStreamSelectionDialog(context, controller);
|
await _showStreamSelectionDialog(context, controller);
|
||||||
case EntryAction.videoSetSpeed:
|
case EntryAction.videoSetSpeed:
|
||||||
await _showSpeedDialog(context, controller);
|
await _showSpeedDialog(context, controller);
|
||||||
|
case EntryAction.videoABRepeat:
|
||||||
|
controller.toggleABRepeat();
|
||||||
case EntryAction.videoSettings:
|
case EntryAction.videoSettings:
|
||||||
await _showSettings(context, controller);
|
await _showSettings(context, controller);
|
||||||
case EntryAction.videoTogglePlay:
|
case EntryAction.videoTogglePlay:
|
||||||
|
|
78
lib/widgets/viewer/overlay/video/ab_repeat.dart
Normal file
78
lib/widgets/viewer/overlay/video/ab_repeat.dart
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
||||||
|
import 'package:aves_video/aves_video.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class VideoABRepeatOverlay extends StatefulWidget {
|
||||||
|
final AvesVideoController? controller;
|
||||||
|
final Animation<double> scale;
|
||||||
|
|
||||||
|
const VideoABRepeatOverlay({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
required this.scale,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _VideoABRepeatOverlayState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VideoABRepeatOverlayState extends State<VideoABRepeatOverlay> {
|
||||||
|
Animation<double> get scale => widget.scale;
|
||||||
|
|
||||||
|
AvesVideoController? get controller => widget.controller;
|
||||||
|
|
||||||
|
ValueNotifier<ABRepeat?> get abRepeatNotifier => controller?.abRepeatNotifier ?? ValueNotifier(null);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
|
||||||
|
return ValueListenableBuilder<ABRepeat?>(
|
||||||
|
valueListenable: abRepeatNotifier,
|
||||||
|
builder: (context, abRepeat, child) {
|
||||||
|
if (abRepeat == null) return const SizedBox();
|
||||||
|
|
||||||
|
Widget boundButton;
|
||||||
|
if (abRepeat.start == null) {
|
||||||
|
boundButton = IconButton(
|
||||||
|
icon: Icon(AIcons.setStart),
|
||||||
|
onPressed: controller?.setABRepeatStart,
|
||||||
|
tooltip: l10n.videoRepeatActionSetStart,
|
||||||
|
);
|
||||||
|
} else if (abRepeat.end == null) {
|
||||||
|
boundButton = IconButton(
|
||||||
|
icon: Icon(AIcons.setEnd),
|
||||||
|
onPressed: controller?.setABRepeatEnd,
|
||||||
|
tooltip: l10n.videoRepeatActionSetEnd,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
boundButton = IconButton(
|
||||||
|
icon: Icon(AIcons.resetBounds),
|
||||||
|
onPressed: controller?.resetABRepeat,
|
||||||
|
tooltip: l10n.resetTooltip,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
OverlayButton(
|
||||||
|
scale: scale,
|
||||||
|
child: boundButton,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
OverlayButton(
|
||||||
|
scale: scale,
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(AIcons.repeatOff),
|
||||||
|
onPressed: () => controller?.toggleABRepeat(),
|
||||||
|
tooltip: l10n.stopTooltip,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,8 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
|
||||||
|
|
||||||
bool get isPlaying => controller?.isPlaying ?? false;
|
bool get isPlaying => controller?.isPlaying ?? false;
|
||||||
|
|
||||||
|
ValueNotifier<ABRepeat?> get abRepeatNotifier => controller?.abRepeatNotifier ?? ValueNotifier(null);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final blurred = settings.enableBlurEffect;
|
final blurred = settings.enableBlurEffect;
|
||||||
|
@ -69,8 +71,7 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: Alignment.center,
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred),
|
color: Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred),
|
||||||
border: AvesBorder.border(context),
|
border: AvesBorder.border(context),
|
||||||
|
@ -80,62 +81,80 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
|
||||||
data: MediaQuery.of(context).copyWith(
|
data: MediaQuery.of(context).copyWith(
|
||||||
textScaler: TextScaler.noScaling,
|
textScaler: TextScaler.noScaling,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: ValueListenableBuilder<ABRepeat?>(
|
||||||
key: _progressBarKey,
|
valueListenable: abRepeatNotifier,
|
||||||
mainAxisSize: MainAxisSize.min,
|
builder: (context, abRepeat, child) {
|
||||||
children: [
|
return Stack(
|
||||||
Row(
|
fit: StackFit.passthrough,
|
||||||
children: [
|
children: [
|
||||||
StreamBuilder<int>(
|
if (abRepeat != null) ...[
|
||||||
stream: positionStream,
|
_buildABRepeatMark(context, abRepeat.start),
|
||||||
builder: (context, snapshot) {
|
_buildABRepeatMark(context, abRepeat.end),
|
||||||
// do not use stream snapshot because it is obsolete when switching between videos
|
],
|
||||||
final position = controller?.currentPosition.floor() ?? 0;
|
Container(
|
||||||
return Text(
|
key: _progressBarKey,
|
||||||
formatFriendlyDuration(Duration(milliseconds: position)),
|
alignment: Alignment.center,
|
||||||
style: textStyle,
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
strutStyle: strutStyle,
|
child: Column(
|
||||||
);
|
mainAxisSize: MainAxisSize.min,
|
||||||
}),
|
children: [
|
||||||
const Spacer(),
|
Row(
|
||||||
Text(
|
children: [
|
||||||
formatFriendlyDuration(Duration(milliseconds: controller?.duration ?? 0)),
|
StreamBuilder<int>(
|
||||||
style: textStyle,
|
stream: positionStream,
|
||||||
strutStyle: strutStyle,
|
builder: (context, snapshot) {
|
||||||
|
// do not use stream snapshot because it is obsolete when switching between videos
|
||||||
|
final position = controller?.currentPosition.floor() ?? 0;
|
||||||
|
return Text(
|
||||||
|
formatFriendlyDuration(Duration(milliseconds: position)),
|
||||||
|
style: textStyle,
|
||||||
|
strutStyle: strutStyle,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
formatFriendlyDuration(Duration(milliseconds: controller?.duration ?? 0)),
|
||||||
|
style: textStyle,
|
||||||
|
strutStyle: strutStyle,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
|
child: Directionality(
|
||||||
|
// force directionality for `LinearProgressIndicator`
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: StreamBuilder<int>(
|
||||||
|
stream: positionStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
// do not use stream snapshot because it is obsolete when switching between videos
|
||||||
|
var progress = controller?.progress ?? 0.0;
|
||||||
|
if (!progress.isFinite) progress = 0.0;
|
||||||
|
return LinearProgressIndicator(
|
||||||
|
value: progress,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(.2),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_buildSpeedIndicator(),
|
||||||
|
_buildMuteIndicator(),
|
||||||
|
Text(
|
||||||
|
// fake text below to match the height of the text above and center the whole thing
|
||||||
|
'',
|
||||||
|
style: textStyle,
|
||||||
|
strutStyle: strutStyle,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
ClipRRect(
|
},
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
|
||||||
child: Directionality(
|
|
||||||
// force directionality for `LinearProgressIndicator`
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
child: StreamBuilder<int>(
|
|
||||||
stream: positionStream,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
// do not use stream snapshot because it is obsolete when switching between videos
|
|
||||||
var progress = controller?.progress ?? 0.0;
|
|
||||||
if (!progress.isFinite) progress = 0.0;
|
|
||||||
return LinearProgressIndicator(
|
|
||||||
value: progress,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.onSurface.withOpacity(.2),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
_buildSpeedIndicator(),
|
|
||||||
_buildMuteIndicator(),
|
|
||||||
Text(
|
|
||||||
// fake text below to match the height of the text above and center the whole thing
|
|
||||||
'',
|
|
||||||
style: textStyle,
|
|
||||||
strutStyle: strutStyle,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -145,6 +164,20 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildABRepeatMark(BuildContext context, int? position) {
|
||||||
|
if (controller == null || position == null) return const SizedBox();
|
||||||
|
return Positioned(
|
||||||
|
left: _progressToDx(position / controller!.duration),
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(left: AvesBorder.straightSide(context, width: 2)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildSpeedIndicator() => StreamBuilder<double>(
|
Widget _buildSpeedIndicator() => StreamBuilder<double>(
|
||||||
stream: controller?.speedStream ?? Stream.value(1.0),
|
stream: controller?.speedStream ?? Stream.value(1.0),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -175,11 +208,20 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
RenderBox? _getProgressBarRenderBox() {
|
||||||
|
return _progressBarKey.currentContext?.findRenderObject() as RenderBox?;
|
||||||
|
}
|
||||||
|
|
||||||
void _seekFromTap(Offset globalPosition) async {
|
void _seekFromTap(Offset globalPosition) async {
|
||||||
if (controller == null) return;
|
final box = _getProgressBarRenderBox();
|
||||||
final keyContext = _progressBarKey.currentContext!;
|
if (controller == null || box == null) return;
|
||||||
final box = keyContext.findRenderObject() as RenderBox;
|
|
||||||
final localPosition = box.globalToLocal(globalPosition);
|
final dx = box.globalToLocal(globalPosition).dx;
|
||||||
await controller!.seekToProgress(localPosition.dx / box.size.width);
|
await controller!.seekToProgress(dx / box.size.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
double? _progressToDx(double progress) {
|
||||||
|
final box = _getProgressBarRenderBox();
|
||||||
|
return box == null ? null : progress * box.size.width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/view/view.dart';
|
import 'package:aves/view/view.dart';
|
||||||
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
||||||
|
import 'package:aves/widgets/viewer/overlay/video/ab_repeat.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/video/controls.dart';
|
import 'package:aves/widgets/viewer/overlay/video/controls.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/video/progress_bar.dart';
|
import 'package:aves/widgets/viewer/overlay/video/progress_bar.dart';
|
||||||
import 'package:aves_model/aves_model.dart';
|
import 'package:aves_model/aves_model.dart';
|
||||||
|
@ -64,19 +65,28 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Row(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
VideoABRepeatOverlay(
|
||||||
child: VideoProgressBar(
|
|
||||||
controller: controller,
|
|
||||||
scale: scale,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
VideoControlRow(
|
|
||||||
entry: entry,
|
|
||||||
controller: controller,
|
controller: controller,
|
||||||
scale: scale,
|
scale: scale,
|
||||||
onActionSelected: widget.onActionSelected,
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: VideoProgressBar(
|
||||||
|
controller: controller,
|
||||||
|
scale: scale,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VideoControlRow(
|
||||||
|
entry: entry,
|
||||||
|
controller: controller,
|
||||||
|
scale: scale,
|
||||||
|
onActionSelected: widget.onActionSelected,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,6 +22,7 @@ enum EntryAction {
|
||||||
videoCaptureFrame,
|
videoCaptureFrame,
|
||||||
videoSelectStreams,
|
videoSelectStreams,
|
||||||
videoSetSpeed,
|
videoSetSpeed,
|
||||||
|
videoABRepeat,
|
||||||
videoToggleMute,
|
videoToggleMute,
|
||||||
videoSettings,
|
videoSettings,
|
||||||
videoTogglePlay,
|
videoTogglePlay,
|
||||||
|
@ -90,6 +91,7 @@ class EntryActions {
|
||||||
EntryAction.videoCaptureFrame,
|
EntryAction.videoCaptureFrame,
|
||||||
EntryAction.videoSelectStreams,
|
EntryAction.videoSelectStreams,
|
||||||
EntryAction.videoSetSpeed,
|
EntryAction.videoSetSpeed,
|
||||||
|
EntryAction.videoABRepeat,
|
||||||
EntryAction.videoToggleMute,
|
EntryAction.videoToggleMute,
|
||||||
EntryAction.videoSettings,
|
EntryAction.videoSettings,
|
||||||
EntryAction.videoTogglePlay,
|
EntryAction.videoTogglePlay,
|
||||||
|
@ -110,6 +112,7 @@ class EntryActions {
|
||||||
EntryAction.videoCaptureFrame,
|
EntryAction.videoCaptureFrame,
|
||||||
EntryAction.videoToggleMute,
|
EntryAction.videoToggleMute,
|
||||||
EntryAction.videoSetSpeed,
|
EntryAction.videoSetSpeed,
|
||||||
|
EntryAction.videoABRepeat,
|
||||||
EntryAction.videoSelectStreams,
|
EntryAction.videoSelectStreams,
|
||||||
EntryAction.videoSettings,
|
EntryAction.videoSettings,
|
||||||
EntryAction.lockViewer,
|
EntryAction.lockViewer,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
library aves_video;
|
library aves_video;
|
||||||
|
|
||||||
|
export 'src/ab_repeat.dart';
|
||||||
export 'src/controller.dart';
|
export 'src/controller.dart';
|
||||||
export 'src/metadata.dart';
|
export 'src/metadata.dart';
|
||||||
export 'src/settings/subtitles.dart';
|
export 'src/settings/subtitles.dart';
|
||||||
|
|
33
plugins/aves_video/lib/src/ab_repeat.dart
Normal file
33
plugins/aves_video/lib/src/ab_repeat.dart
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class ABRepeat {
|
||||||
|
final int? start, end;
|
||||||
|
|
||||||
|
ABRepeat({this.start, this.end});
|
||||||
|
|
||||||
|
ABRepeat sanitize() {
|
||||||
|
if (start != null && end != null && start! > end!) {
|
||||||
|
return ABRepeat(start: end, end: start);
|
||||||
|
}
|
||||||
|
return ABRepeat(start: start, end: end);
|
||||||
|
}
|
||||||
|
|
||||||
|
int clamp(int position) => (start != null && end != null) ? position.clamp(start!, end!) : position;
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin ABRepeatMixin {
|
||||||
|
int get currentPosition;
|
||||||
|
|
||||||
|
ValueNotifier<ABRepeat?> abRepeatNotifier = ValueNotifier(null);
|
||||||
|
|
||||||
|
void toggleABRepeat() => _setAbRepeat(abRepeatNotifier.value != null ? null : ABRepeat());
|
||||||
|
|
||||||
|
void resetABRepeat() => _setAbRepeat(ABRepeat());
|
||||||
|
|
||||||
|
void setABRepeatStart() => _setAbRepeat(ABRepeat(start: currentPosition, end: abRepeatNotifier.value?.end));
|
||||||
|
|
||||||
|
void setABRepeatEnd() => _setAbRepeat(ABRepeat(start: abRepeatNotifier.value?.start, end: currentPosition));
|
||||||
|
|
||||||
|
void _setAbRepeat(ABRepeat? v) => abRepeatNotifier.value = v?.sanitize();
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ abstract class AvesVideoControllerFactory {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AvesVideoController {
|
abstract class AvesVideoController with ABRepeatMixin {
|
||||||
final AvesEntryBase _entry;
|
final AvesEntryBase _entry;
|
||||||
final PlaybackStateHandler playbackStateHandler;
|
final PlaybackStateHandler playbackStateHandler;
|
||||||
final VideoSettings settings;
|
final VideoSettings settings;
|
||||||
|
@ -96,6 +96,7 @@ abstract class AvesVideoController {
|
||||||
|
|
||||||
int get duration;
|
int get duration;
|
||||||
|
|
||||||
|
@override
|
||||||
int get currentPosition;
|
int get currentPosition;
|
||||||
|
|
||||||
double get progress {
|
double get progress {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||||
import 'package:aves_model/aves_model.dart';
|
import 'package:aves_model/aves_model.dart';
|
||||||
import 'package:aves_utils/aves_utils.dart';
|
import 'package:aves_utils/aves_utils.dart';
|
||||||
import 'package:aves_video/aves_video.dart';
|
import 'package:aves_video/aves_video.dart';
|
||||||
|
import 'package:aves_video_mpv/src/tracks.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -12,7 +13,7 @@ import 'package:media_kit_video/media_kit_video.dart';
|
||||||
class MpvVideoController extends AvesVideoController {
|
class MpvVideoController extends AvesVideoController {
|
||||||
late Player _instance;
|
late Player _instance;
|
||||||
late VideoStatus _status;
|
late VideoStatus _status;
|
||||||
bool _firstFrameRendered = false;
|
bool _firstFrameRendered = false, _abRepeatSeeking = false;
|
||||||
final ValueNotifier<VideoController?> _controllerNotifier = ValueNotifier(null);
|
final ValueNotifier<VideoController?> _controllerNotifier = ValueNotifier(null);
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
final StreamController<VideoStatus> _statusStreamController = StreamController.broadcast();
|
final StreamController<VideoStatus> _statusStreamController = StreamController.broadcast();
|
||||||
|
@ -82,22 +83,41 @@ class MpvVideoController extends AvesVideoController {
|
||||||
|
|
||||||
void _startListening() {
|
void _startListening() {
|
||||||
_subscriptions.add(statusStream.listen((v) => _status = v));
|
_subscriptions.add(statusStream.listen((v) => _status = v));
|
||||||
_subscriptions.add(_instance.stream.completed.listen((v) {
|
|
||||||
if (v) {
|
final playerStream = _instance.stream;
|
||||||
|
_subscriptions.add(playerStream.completed.listen((completed) {
|
||||||
|
if (completed) {
|
||||||
_statusStreamController.add(VideoStatus.completed);
|
_statusStreamController.add(VideoStatus.completed);
|
||||||
_completedNotifier.notify();
|
_completedNotifier.notify();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
_subscriptions.add(_instance.stream.playing.listen((v) {
|
_subscriptions.add(playerStream.playing.listen((playing) {
|
||||||
if (status == VideoStatus.idle) return;
|
if (status == VideoStatus.idle) return;
|
||||||
_statusStreamController.add(v ? VideoStatus.playing : VideoStatus.paused);
|
_statusStreamController.add(playing ? VideoStatus.playing : VideoStatus.paused);
|
||||||
}));
|
}));
|
||||||
_subscriptions.add(_instance.stream.subtitle.listen((v) => _timedTextStreamController.add(v.isEmpty ? null : v[0])));
|
_subscriptions.add(playerStream.position.listen((v) {
|
||||||
_subscriptions.add(_instance.stream.videoParams.listen((v) => sarNotifier.value = v.par));
|
final abRepeat = abRepeatNotifier.value;
|
||||||
_subscriptions.add(_instance.stream.log.listen((v) => debugPrint('libmpv log: $v')));
|
if (abRepeat != null && status == VideoStatus.playing) {
|
||||||
_subscriptions.add(_instance.stream.error.listen((v) => debugPrint('libmpv error: $v')));
|
final start = abRepeat.start;
|
||||||
_subscriptions.add(settings.updateStream.where((event) => event.key == SettingKeys.enableVideoHardwareAccelerationKey).listen((_) => _initController()));
|
final end = abRepeat.end;
|
||||||
_subscriptions.add(settings.updateStream.where((event) => event.key == SettingKeys.videoLoopModeKey).listen((_) => _applyLoop()));
|
if (start != null && end != null) {
|
||||||
|
if (v.inMilliseconds < end) {
|
||||||
|
_abRepeatSeeking = false;
|
||||||
|
} else if (!_abRepeatSeeking) {
|
||||||
|
_abRepeatSeeking = true;
|
||||||
|
_instance.seek(Duration(milliseconds: start));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
_subscriptions.add(playerStream.subtitle.listen((v) => _timedTextStreamController.add(v.isEmpty ? null : v[0])));
|
||||||
|
_subscriptions.add(playerStream.videoParams.listen((v) => sarNotifier.value = v.par));
|
||||||
|
_subscriptions.add(playerStream.log.listen((v) => debugPrint('libmpv log: $v')));
|
||||||
|
_subscriptions.add(playerStream.error.listen((v) => debugPrint('libmpv error: $v')));
|
||||||
|
|
||||||
|
final settingsStream = settings.updateStream;
|
||||||
|
_subscriptions.add(settingsStream.where((event) => event.key == SettingKeys.enableVideoHardwareAccelerationKey).listen((_) => _initController()));
|
||||||
|
_subscriptions.add(settingsStream.where((event) => event.key == SettingKeys.videoLoopModeKey).listen((_) => _applyLoop()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _stopListening() {
|
void _stopListening() {
|
||||||
|
@ -160,6 +180,7 @@ class MpvVideoController extends AvesVideoController {
|
||||||
// and `PlayerConfiguration.ready` hook is useless.
|
// and `PlayerConfiguration.ready` hook is useless.
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
}
|
}
|
||||||
|
targetMillis = abRepeatNotifier.value?.clamp(targetMillis) ?? targetMillis;
|
||||||
await _instance.seek(Duration(milliseconds: targetMillis));
|
await _instance.seek(Duration(milliseconds: targetMillis));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,45 +376,3 @@ class MpvVideoController extends AvesVideoController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ExtraVideoTrack on VideoTrack {
|
|
||||||
MediaStreamSummary toAves(int index) {
|
|
||||||
return MediaStreamSummary(
|
|
||||||
type: MediaStreamType.video,
|
|
||||||
index: index,
|
|
||||||
codecName: null,
|
|
||||||
language: language,
|
|
||||||
title: title,
|
|
||||||
width: null,
|
|
||||||
height: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ExtraAudioTrack on AudioTrack {
|
|
||||||
MediaStreamSummary toAves(int index) {
|
|
||||||
return MediaStreamSummary(
|
|
||||||
type: MediaStreamType.audio,
|
|
||||||
index: index,
|
|
||||||
codecName: null,
|
|
||||||
language: language,
|
|
||||||
title: title,
|
|
||||||
width: null,
|
|
||||||
height: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ExtraSubtitleTrack on SubtitleTrack {
|
|
||||||
MediaStreamSummary toAves(int index) {
|
|
||||||
return MediaStreamSummary(
|
|
||||||
type: MediaStreamType.text,
|
|
||||||
index: index,
|
|
||||||
codecName: null,
|
|
||||||
language: language,
|
|
||||||
title: title,
|
|
||||||
width: null,
|
|
||||||
height: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
44
plugins/aves_video_mpv/lib/src/tracks.dart
Normal file
44
plugins/aves_video_mpv/lib/src/tracks.dart
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:aves_video/aves_video.dart';
|
||||||
|
import 'package:media_kit/media_kit.dart';
|
||||||
|
|
||||||
|
extension ExtraVideoTrack on VideoTrack {
|
||||||
|
MediaStreamSummary toAves(int index) {
|
||||||
|
return MediaStreamSummary(
|
||||||
|
type: MediaStreamType.video,
|
||||||
|
index: index,
|
||||||
|
codecName: null,
|
||||||
|
language: language,
|
||||||
|
title: title,
|
||||||
|
width: null,
|
||||||
|
height: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ExtraAudioTrack on AudioTrack {
|
||||||
|
MediaStreamSummary toAves(int index) {
|
||||||
|
return MediaStreamSummary(
|
||||||
|
type: MediaStreamType.audio,
|
||||||
|
index: index,
|
||||||
|
codecName: null,
|
||||||
|
language: language,
|
||||||
|
title: title,
|
||||||
|
width: null,
|
||||||
|
height: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ExtraSubtitleTrack on SubtitleTrack {
|
||||||
|
MediaStreamSummary toAves(int index) {
|
||||||
|
return MediaStreamSummary(
|
||||||
|
type: MediaStreamType.text,
|
||||||
|
index: index,
|
||||||
|
codecName: null,
|
||||||
|
language: language,
|
||||||
|
title: title,
|
||||||
|
width: null,
|
||||||
|
height: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,18 @@
|
||||||
{
|
{
|
||||||
|
"ar": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
|
"be": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
"bn": [
|
"bn": [
|
||||||
"itemCount",
|
"itemCount",
|
||||||
"columnCount",
|
"columnCount",
|
||||||
|
@ -7,6 +21,7 @@
|
||||||
"timeDays",
|
"timeDays",
|
||||||
"focalLength",
|
"focalLength",
|
||||||
"resetTooltip",
|
"resetTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"doubleBackExitMessage",
|
"doubleBackExitMessage",
|
||||||
"sourceStateLocatingCountries",
|
"sourceStateLocatingCountries",
|
||||||
"sourceStateLocatingPlaces",
|
"sourceStateLocatingPlaces",
|
||||||
|
@ -62,6 +77,9 @@
|
||||||
"videoActionSkip10",
|
"videoActionSkip10",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
"videoActionSetSpeed",
|
"videoActionSetSpeed",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionSettings",
|
"viewerActionSettings",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -659,7 +677,15 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"ca": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
"ckb": [
|
"ckb": [
|
||||||
|
"stopTooltip",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
"entryActionFlip",
|
"entryActionFlip",
|
||||||
|
@ -668,6 +694,9 @@
|
||||||
"entryActionCast",
|
"entryActionCast",
|
||||||
"videoActionCaptureFrame",
|
"videoActionCaptureFrame",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
"slideshowActionResume",
|
"slideshowActionResume",
|
||||||
|
@ -1199,6 +1228,10 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"cs": [
|
"cs": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"collectionActionSetHome",
|
"collectionActionSetHome",
|
||||||
"setHomeCustomCollection",
|
"setHomeCustomCollection",
|
||||||
"settingsThumbnailShowHdrIcon"
|
"settingsThumbnailShowHdrIcon"
|
||||||
|
@ -1233,6 +1266,7 @@
|
||||||
"actionRemove",
|
"actionRemove",
|
||||||
"resetTooltip",
|
"resetTooltip",
|
||||||
"saveTooltip",
|
"saveTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"pickTooltip",
|
"pickTooltip",
|
||||||
"doubleBackExitMessage",
|
"doubleBackExitMessage",
|
||||||
"doNotAskAgain",
|
"doNotAskAgain",
|
||||||
|
@ -1292,6 +1326,9 @@
|
||||||
"videoActionSkip10",
|
"videoActionSkip10",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
"videoActionSetSpeed",
|
"videoActionSetSpeed",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionSettings",
|
"viewerActionSettings",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -1889,8 +1926,19 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"de": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
"el": [
|
"el": [
|
||||||
|
"stopTooltip",
|
||||||
"entryActionCast",
|
"entryActionCast",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"castDialogTitle",
|
"castDialogTitle",
|
||||||
"aboutDataUsageSectionTitle",
|
"aboutDataUsageSectionTitle",
|
||||||
"aboutDataUsageData",
|
"aboutDataUsageData",
|
||||||
|
@ -1906,7 +1954,25 @@
|
||||||
"settingsViewerShowHistogram"
|
"settingsViewerShowHistogram"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"es": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
|
"eu": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
"fa": [
|
"fa": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"coordinateFormatDms",
|
"coordinateFormatDms",
|
||||||
"coordinateDms",
|
"coordinateDms",
|
||||||
"coordinateDmsNorth",
|
"coordinateDmsNorth",
|
||||||
|
@ -2346,11 +2412,15 @@
|
||||||
"fi": [
|
"fi": [
|
||||||
"focalLength",
|
"focalLength",
|
||||||
"clearTooltip",
|
"clearTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"chipActionFilterIn",
|
"chipActionFilterIn",
|
||||||
"entryActionSetAs",
|
"entryActionSetAs",
|
||||||
"entryActionCast",
|
"entryActionCast",
|
||||||
"videoActionUnmute",
|
"videoActionUnmute",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"filterTypeRawLabel",
|
"filterTypeRawLabel",
|
||||||
"filterTypeSphericalVideoLabel",
|
"filterTypeSphericalVideoLabel",
|
||||||
"filterTypeGeotiffLabel",
|
"filterTypeGeotiffLabel",
|
||||||
|
@ -2887,10 +2957,18 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"fr": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
"gl": [
|
"gl": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
"saveCopyButtonLabel",
|
"saveCopyButtonLabel",
|
||||||
"applyTooltip",
|
"applyTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
|
@ -2899,6 +2977,9 @@
|
||||||
"entryActionShareImageOnly",
|
"entryActionShareImageOnly",
|
||||||
"entryActionShareVideoOnly",
|
"entryActionShareVideoOnly",
|
||||||
"entryActionCast",
|
"entryActionCast",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
"entryInfoActionExportMetadata",
|
"entryInfoActionExportMetadata",
|
||||||
|
@ -3465,6 +3546,7 @@
|
||||||
"actionRemove",
|
"actionRemove",
|
||||||
"resetTooltip",
|
"resetTooltip",
|
||||||
"saveTooltip",
|
"saveTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"pickTooltip",
|
"pickTooltip",
|
||||||
"doubleBackExitMessage",
|
"doubleBackExitMessage",
|
||||||
"doNotAskAgain",
|
"doNotAskAgain",
|
||||||
|
@ -3524,6 +3606,9 @@
|
||||||
"videoActionSkip10",
|
"videoActionSkip10",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
"videoActionSetSpeed",
|
"videoActionSetSpeed",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionSettings",
|
"viewerActionSettings",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -4122,6 +4207,7 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"hi": [
|
"hi": [
|
||||||
|
"stopTooltip",
|
||||||
"sourceStateCataloguing",
|
"sourceStateCataloguing",
|
||||||
"sourceStateLocatingCountries",
|
"sourceStateLocatingCountries",
|
||||||
"sourceStateLocatingPlaces",
|
"sourceStateLocatingPlaces",
|
||||||
|
@ -4171,6 +4257,9 @@
|
||||||
"videoActionSkip10",
|
"videoActionSkip10",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
"videoActionSetSpeed",
|
"videoActionSetSpeed",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionSettings",
|
"viewerActionSettings",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -4768,7 +4857,32 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"hu": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
|
"id": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
|
"is": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"collectionActionSetHome",
|
"collectionActionSetHome",
|
||||||
"setHomeCustomCollection",
|
"setHomeCustomCollection",
|
||||||
"settingsThumbnailShowHdrIcon"
|
"settingsThumbnailShowHdrIcon"
|
||||||
|
@ -4776,8 +4890,12 @@
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
"applyTooltip",
|
"applyTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
"editorActionTransform",
|
"editorActionTransform",
|
||||||
|
@ -4844,6 +4962,7 @@
|
||||||
"timeMinutes",
|
"timeMinutes",
|
||||||
"timeDays",
|
"timeDays",
|
||||||
"focalLength",
|
"focalLength",
|
||||||
|
"stopTooltip",
|
||||||
"chipActionGoToCountryPage",
|
"chipActionGoToCountryPage",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionGoToTagPage",
|
"chipActionGoToTagPage",
|
||||||
|
@ -4894,6 +5013,9 @@
|
||||||
"videoActionSkip10",
|
"videoActionSkip10",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
"videoActionSetSpeed",
|
"videoActionSetSpeed",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionSettings",
|
"viewerActionSettings",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -5491,16 +5613,27 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"ko": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
"lt": [
|
"lt": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
"saveCopyButtonLabel",
|
"saveCopyButtonLabel",
|
||||||
"applyTooltip",
|
"applyTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
"entryActionCast",
|
"entryActionCast",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
"editorActionTransform",
|
"editorActionTransform",
|
||||||
|
@ -5613,6 +5746,7 @@
|
||||||
"actionRemove",
|
"actionRemove",
|
||||||
"resetTooltip",
|
"resetTooltip",
|
||||||
"saveTooltip",
|
"saveTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"pickTooltip",
|
"pickTooltip",
|
||||||
"doubleBackExitMessage",
|
"doubleBackExitMessage",
|
||||||
"doNotAskAgain",
|
"doNotAskAgain",
|
||||||
|
@ -5672,6 +5806,9 @@
|
||||||
"videoActionSkip10",
|
"videoActionSkip10",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
"videoActionSetSpeed",
|
"videoActionSetSpeed",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionSettings",
|
"viewerActionSettings",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -6270,7 +6407,11 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"my": [
|
"my": [
|
||||||
|
"stopTooltip",
|
||||||
"entryActionCast",
|
"entryActionCast",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"accessibilityAnimationsRemove",
|
"accessibilityAnimationsRemove",
|
||||||
"accessibilityAnimationsKeep",
|
"accessibilityAnimationsKeep",
|
||||||
"overlayHistogramLuminance",
|
"overlayHistogramLuminance",
|
||||||
|
@ -6385,7 +6526,11 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"nb": [
|
"nb": [
|
||||||
|
"stopTooltip",
|
||||||
"entryActionCast",
|
"entryActionCast",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
"editorActionTransform",
|
"editorActionTransform",
|
||||||
|
@ -6412,12 +6557,16 @@
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
|
"stopTooltip",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
"entryActionShareImageOnly",
|
"entryActionShareImageOnly",
|
||||||
"entryActionShareVideoOnly",
|
"entryActionShareVideoOnly",
|
||||||
"entryActionCast",
|
"entryActionCast",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
"editorActionTransform",
|
"editorActionTransform",
|
||||||
|
@ -6500,8 +6649,12 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"nn": [
|
"nn": [
|
||||||
|
"stopTooltip",
|
||||||
"sourceStateCataloguing",
|
"sourceStateCataloguing",
|
||||||
"entryActionCast",
|
"entryActionCast",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"accessibilityAnimationsKeep",
|
"accessibilityAnimationsKeep",
|
||||||
"overlayHistogramNone",
|
"overlayHistogramNone",
|
||||||
"overlayHistogramRGB",
|
"overlayHistogramRGB",
|
||||||
|
@ -6566,6 +6719,7 @@
|
||||||
"actionRemove",
|
"actionRemove",
|
||||||
"resetTooltip",
|
"resetTooltip",
|
||||||
"saveTooltip",
|
"saveTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"pickTooltip",
|
"pickTooltip",
|
||||||
"doubleBackExitMessage",
|
"doubleBackExitMessage",
|
||||||
"doNotAskAgain",
|
"doNotAskAgain",
|
||||||
|
@ -6621,6 +6775,9 @@
|
||||||
"videoActionSkip10",
|
"videoActionSkip10",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
"videoActionSetSpeed",
|
"videoActionSetSpeed",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionSettings",
|
"viewerActionSettings",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -7154,12 +7311,37 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"pl": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"collectionActionSetHome",
|
"collectionActionSetHome",
|
||||||
"setHomeCustomCollection",
|
"setHomeCustomCollection",
|
||||||
"settingsThumbnailShowHdrIcon"
|
"settingsThumbnailShowHdrIcon"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"ro": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ru": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
"sat": [
|
"sat": [
|
||||||
"welcomeOptional",
|
"welcomeOptional",
|
||||||
"welcomeTermsToggle",
|
"welcomeTermsToggle",
|
||||||
|
@ -7187,6 +7369,7 @@
|
||||||
"actionRemove",
|
"actionRemove",
|
||||||
"resetTooltip",
|
"resetTooltip",
|
||||||
"saveTooltip",
|
"saveTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"pickTooltip",
|
"pickTooltip",
|
||||||
"doubleBackExitMessage",
|
"doubleBackExitMessage",
|
||||||
"doNotAskAgain",
|
"doNotAskAgain",
|
||||||
|
@ -7246,6 +7429,9 @@
|
||||||
"videoActionSkip10",
|
"videoActionSkip10",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
"videoActionSetSpeed",
|
"videoActionSetSpeed",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionSettings",
|
"viewerActionSettings",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -7843,6 +8029,13 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"sk": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
"sl": [
|
"sl": [
|
||||||
"itemCount",
|
"itemCount",
|
||||||
"columnCount",
|
"columnCount",
|
||||||
|
@ -7868,6 +8061,7 @@
|
||||||
"actionRemove",
|
"actionRemove",
|
||||||
"resetTooltip",
|
"resetTooltip",
|
||||||
"saveTooltip",
|
"saveTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"pickTooltip",
|
"pickTooltip",
|
||||||
"doubleBackExitMessage",
|
"doubleBackExitMessage",
|
||||||
"doNotAskAgain",
|
"doNotAskAgain",
|
||||||
|
@ -7927,6 +8121,9 @@
|
||||||
"videoActionSkip10",
|
"videoActionSkip10",
|
||||||
"videoActionSelectStreams",
|
"videoActionSelectStreams",
|
||||||
"videoActionSetSpeed",
|
"videoActionSetSpeed",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionSettings",
|
"viewerActionSettings",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
|
@ -8525,6 +8722,10 @@
|
||||||
],
|
],
|
||||||
|
|
||||||
"sv": [
|
"sv": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"widgetOpenPageViewer",
|
"widgetOpenPageViewer",
|
||||||
"rootDirectoryDescription",
|
"rootDirectoryDescription",
|
||||||
"restrictedAccessDialogMessage",
|
"restrictedAccessDialogMessage",
|
||||||
|
@ -8841,12 +9042,16 @@
|
||||||
"applyButtonLabel",
|
"applyButtonLabel",
|
||||||
"saveCopyButtonLabel",
|
"saveCopyButtonLabel",
|
||||||
"applyTooltip",
|
"applyTooltip",
|
||||||
|
"stopTooltip",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
"chipActionLock",
|
"chipActionLock",
|
||||||
"chipActionShowCountryStates",
|
"chipActionShowCountryStates",
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
"entryActionCast",
|
"entryActionCast",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd",
|
||||||
"viewerActionLock",
|
"viewerActionLock",
|
||||||
"viewerActionUnlock",
|
"viewerActionUnlock",
|
||||||
"editorActionTransform",
|
"editorActionTransform",
|
||||||
|
@ -9234,5 +9439,40 @@
|
||||||
"filePickerOpenFrom",
|
"filePickerOpenFrom",
|
||||||
"filePickerNoItems",
|
"filePickerNoItems",
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
|
],
|
||||||
|
|
||||||
|
"tr": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
|
"uk": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
|
"vi": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
|
"zh": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
|
],
|
||||||
|
|
||||||
|
"zh_Hant": [
|
||||||
|
"stopTooltip",
|
||||||
|
"videoActionABRepeat",
|
||||||
|
"videoRepeatActionSetStart",
|
||||||
|
"videoRepeatActionSetEnd"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue