#962 video: A-B repeat

This commit is contained in:
Thibault Deckers 2024-03-31 00:17:37 +01:00
parent 8555322460
commit 46aef919be
16 changed files with 572 additions and 124 deletions

View file

@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
- Collection: support for Fairphone burst pattern
- Collection: allow using tags/make/model when bulk renaming
- Video: A-B repeat
- Settings: hidden items can be toggled
### Changed

View file

@ -63,6 +63,7 @@
"actionRemove": "Remove",
"resetTooltip": "Reset",
"saveTooltip": "Save",
"stopTooltip": "Stop",
"pickTooltip": "Pick",
"doubleBackExitMessage": "Tap “back” again to exit.",
@ -127,6 +128,10 @@
"videoActionSkip10": "Seek forward 10 seconds",
"videoActionSelectStreams": "Select tracks",
"videoActionSetSpeed": "Playback speed",
"videoActionABRepeat": "A-B repeat",
"videoRepeatActionSetStart": "Set start",
"videoRepeatActionSetEnd": "Set end",
"viewerActionSettings": "Settings",
"viewerActionLock": "Lock viewer",

View file

@ -119,7 +119,10 @@ class AIcons {
static const pause = Icons.pause;
static const print = Icons.print_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 final resetBounds = MdiIcons.rayStartEnd;
static const reverse = Icons.invert_colors_outlined;
static const skip10 = Icons.forward_10_outlined;
static const reset = Icons.restart_alt_outlined;
@ -131,6 +134,8 @@ class AIcons {
static const select = Icons.select_all_outlined;
static const setAs = Icons.wallpaper_outlined;
static final setCover = MdiIcons.imageEditOutline;
static final setEnd = MdiIcons.rayEnd;
static final setStart = MdiIcons.rayStart;
static const share = Icons.share_outlined;
static const show = Icons.visibility_outlined;
static final showFullscreen = MdiIcons.arrowExpand;

View file

@ -36,6 +36,7 @@ extension ExtraEntryActionView on EntryAction {
l10n.videoActionMute,
EntryAction.videoSelectStreams => l10n.videoActionSelectStreams,
EntryAction.videoSetSpeed => l10n.videoActionSetSpeed,
EntryAction.videoABRepeat => l10n.videoActionABRepeat,
EntryAction.videoSettings => l10n.viewerActionSettings,
EntryAction.videoTogglePlay =>
// different data depending on toggle state
@ -110,6 +111,7 @@ extension ExtraEntryActionView on EntryAction {
AIcons.mute,
EntryAction.videoSelectStreams => AIcons.streams,
EntryAction.videoSetSpeed => AIcons.speed,
EntryAction.videoABRepeat => AIcons.repeat,
EntryAction.videoSettings => AIcons.videoSettings,
EntryAction.videoTogglePlay =>
// different data depending on toggle state

View file

@ -94,6 +94,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
return !settings.useTvLayout && targetEntry.isPureVideo;
case EntryAction.videoSelectStreams:
case EntryAction.videoSetSpeed:
case EntryAction.videoABRepeat:
case EntryAction.videoSettings:
case EntryAction.videoTogglePlay:
case EntryAction.videoReplay10:
@ -229,6 +230,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
case EntryAction.videoToggleMute:
case EntryAction.videoSelectStreams:
case EntryAction.videoSetSpeed:
case EntryAction.videoABRepeat:
case EntryAction.videoSettings:
case EntryAction.videoTogglePlay:
case EntryAction.videoReplay10:

View file

@ -64,6 +64,8 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
await _showStreamSelectionDialog(context, controller);
case EntryAction.videoSetSpeed:
await _showSpeedDialog(context, controller);
case EntryAction.videoABRepeat:
controller.toggleABRepeat();
case EntryAction.videoSettings:
await _showSettings(context, controller);
case EntryAction.videoTogglePlay:

View 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,
),
),
],
);
},
);
}
}

View file

@ -37,6 +37,8 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
bool get isPlaying => controller?.isPlaying ?? false;
ValueNotifier<ABRepeat?> get abRepeatNotifier => controller?.abRepeatNotifier ?? ValueNotifier(null);
@override
Widget build(BuildContext context) {
final blurred = settings.enableBlurEffect;
@ -69,8 +71,7 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Themes.overlayBackgroundColor(brightness: theme.brightness, blurred: blurred),
border: AvesBorder.border(context),
@ -80,8 +81,21 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.noScaling,
),
child: Column(
child: ValueListenableBuilder<ABRepeat?>(
valueListenable: abRepeatNotifier,
builder: (context, abRepeat, child) {
return Stack(
fit: StackFit.passthrough,
children: [
if (abRepeat != null) ...[
_buildABRepeatMark(context, abRepeat.start),
_buildABRepeatMark(context, abRepeat.end),
],
Container(
key: _progressBarKey,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 4),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
@ -138,10 +152,29 @@ 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)),
),
),
);
}
@ -175,11 +208,20 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
},
);
RenderBox? _getProgressBarRenderBox() {
return _progressBarKey.currentContext?.findRenderObject() as RenderBox?;
}
void _seekFromTap(Offset globalPosition) async {
if (controller == null) return;
final keyContext = _progressBarKey.currentContext!;
final box = keyContext.findRenderObject() as RenderBox;
final localPosition = box.globalToLocal(globalPosition);
await controller!.seekToProgress(localPosition.dx / box.size.width);
final box = _getProgressBarRenderBox();
if (controller == null || box == null) return;
final dx = box.globalToLocal(globalPosition).dx;
await controller!.seekToProgress(dx / box.size.width);
}
double? _progressToDx(double progress) {
final box = _getProgressBarRenderBox();
return box == null ? null : progress * box.size.width;
}
}

View file

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/view/view.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/progress_bar.dart';
import 'package:aves_model/aves_model.dart';
@ -64,7 +65,14 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
);
}
return Row(
return Column(
children: [
VideoABRepeatOverlay(
controller: controller,
scale: scale,
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: VideoProgressBar(
@ -79,6 +87,8 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
onActionSelected: widget.onActionSelected,
),
],
),
],
);
},
),

View file

@ -22,6 +22,7 @@ enum EntryAction {
videoCaptureFrame,
videoSelectStreams,
videoSetSpeed,
videoABRepeat,
videoToggleMute,
videoSettings,
videoTogglePlay,
@ -90,6 +91,7 @@ class EntryActions {
EntryAction.videoCaptureFrame,
EntryAction.videoSelectStreams,
EntryAction.videoSetSpeed,
EntryAction.videoABRepeat,
EntryAction.videoToggleMute,
EntryAction.videoSettings,
EntryAction.videoTogglePlay,
@ -110,6 +112,7 @@ class EntryActions {
EntryAction.videoCaptureFrame,
EntryAction.videoToggleMute,
EntryAction.videoSetSpeed,
EntryAction.videoABRepeat,
EntryAction.videoSelectStreams,
EntryAction.videoSettings,
EntryAction.lockViewer,

View file

@ -1,5 +1,6 @@
library aves_video;
export 'src/ab_repeat.dart';
export 'src/controller.dart';
export 'src/metadata.dart';
export 'src/settings/subtitles.dart';

View 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();
}

View file

@ -15,7 +15,7 @@ abstract class AvesVideoControllerFactory {
});
}
abstract class AvesVideoController {
abstract class AvesVideoController with ABRepeatMixin {
final AvesEntryBase _entry;
final PlaybackStateHandler playbackStateHandler;
final VideoSettings settings;
@ -96,6 +96,7 @@ abstract class AvesVideoController {
int get duration;
@override
int get currentPosition;
double get progress {

View file

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:aves_model/aves_model.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:aves_video/aves_video.dart';
import 'package:aves_video_mpv/src/tracks.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -12,7 +13,7 @@ import 'package:media_kit_video/media_kit_video.dart';
class MpvVideoController extends AvesVideoController {
late Player _instance;
late VideoStatus _status;
bool _firstFrameRendered = false;
bool _firstFrameRendered = false, _abRepeatSeeking = false;
final ValueNotifier<VideoController?> _controllerNotifier = ValueNotifier(null);
final List<StreamSubscription> _subscriptions = [];
final StreamController<VideoStatus> _statusStreamController = StreamController.broadcast();
@ -82,22 +83,41 @@ class MpvVideoController extends AvesVideoController {
void _startListening() {
_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);
_completedNotifier.notify();
}
}));
_subscriptions.add(_instance.stream.playing.listen((v) {
_subscriptions.add(playerStream.playing.listen((playing) {
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(_instance.stream.videoParams.listen((v) => sarNotifier.value = v.par));
_subscriptions.add(_instance.stream.log.listen((v) => debugPrint('libmpv log: $v')));
_subscriptions.add(_instance.stream.error.listen((v) => debugPrint('libmpv error: $v')));
_subscriptions.add(settings.updateStream.where((event) => event.key == SettingKeys.enableVideoHardwareAccelerationKey).listen((_) => _initController()));
_subscriptions.add(settings.updateStream.where((event) => event.key == SettingKeys.videoLoopModeKey).listen((_) => _applyLoop()));
_subscriptions.add(playerStream.position.listen((v) {
final abRepeat = abRepeatNotifier.value;
if (abRepeat != null && status == VideoStatus.playing) {
final start = abRepeat.start;
final end = abRepeat.end;
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() {
@ -160,6 +180,7 @@ class MpvVideoController extends AvesVideoController {
// and `PlayerConfiguration.ready` hook is useless.
await Future.delayed(const Duration(milliseconds: 500));
}
targetMillis = abRepeatNotifier.value?.clamp(targetMillis) ?? 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,
);
}
}

View 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,
);
}
}

View file

@ -1,4 +1,18 @@
{
"ar": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"be": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"bn": [
"itemCount",
"columnCount",
@ -7,6 +21,7 @@
"timeDays",
"focalLength",
"resetTooltip",
"stopTooltip",
"doubleBackExitMessage",
"sourceStateLocatingCountries",
"sourceStateLocatingPlaces",
@ -62,6 +77,9 @@
"videoActionSkip10",
"videoActionSelectStreams",
"videoActionSetSpeed",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionSettings",
"viewerActionLock",
"viewerActionUnlock",
@ -659,7 +677,15 @@
"filePickerUseThisFolder"
],
"ca": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"ckb": [
"stopTooltip",
"chipActionGoToPlacePage",
"chipActionShowCountryStates",
"entryActionFlip",
@ -668,6 +694,9 @@
"entryActionCast",
"videoActionCaptureFrame",
"videoActionSelectStreams",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionLock",
"viewerActionUnlock",
"slideshowActionResume",
@ -1199,6 +1228,10 @@
],
"cs": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"collectionActionSetHome",
"setHomeCustomCollection",
"settingsThumbnailShowHdrIcon"
@ -1233,6 +1266,7 @@
"actionRemove",
"resetTooltip",
"saveTooltip",
"stopTooltip",
"pickTooltip",
"doubleBackExitMessage",
"doNotAskAgain",
@ -1292,6 +1326,9 @@
"videoActionSkip10",
"videoActionSelectStreams",
"videoActionSetSpeed",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionSettings",
"viewerActionLock",
"viewerActionUnlock",
@ -1889,8 +1926,19 @@
"filePickerUseThisFolder"
],
"de": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"el": [
"stopTooltip",
"entryActionCast",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"castDialogTitle",
"aboutDataUsageSectionTitle",
"aboutDataUsageData",
@ -1906,7 +1954,25 @@
"settingsViewerShowHistogram"
],
"es": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"eu": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"fa": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"coordinateFormatDms",
"coordinateDms",
"coordinateDmsNorth",
@ -2346,11 +2412,15 @@
"fi": [
"focalLength",
"clearTooltip",
"stopTooltip",
"chipActionFilterIn",
"entryActionSetAs",
"entryActionCast",
"videoActionUnmute",
"videoActionSelectStreams",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"filterTypeRawLabel",
"filterTypeSphericalVideoLabel",
"filterTypeGeotiffLabel",
@ -2887,10 +2957,18 @@
"filePickerUseThisFolder"
],
"fr": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"gl": [
"columnCount",
"saveCopyButtonLabel",
"applyTooltip",
"stopTooltip",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",
@ -2899,6 +2977,9 @@
"entryActionShareImageOnly",
"entryActionShareVideoOnly",
"entryActionCast",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionLock",
"viewerActionUnlock",
"entryInfoActionExportMetadata",
@ -3465,6 +3546,7 @@
"actionRemove",
"resetTooltip",
"saveTooltip",
"stopTooltip",
"pickTooltip",
"doubleBackExitMessage",
"doNotAskAgain",
@ -3524,6 +3606,9 @@
"videoActionSkip10",
"videoActionSelectStreams",
"videoActionSetSpeed",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionSettings",
"viewerActionLock",
"viewerActionUnlock",
@ -4122,6 +4207,7 @@
],
"hi": [
"stopTooltip",
"sourceStateCataloguing",
"sourceStateLocatingCountries",
"sourceStateLocatingPlaces",
@ -4171,6 +4257,9 @@
"videoActionSkip10",
"videoActionSelectStreams",
"videoActionSetSpeed",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionSettings",
"viewerActionLock",
"viewerActionUnlock",
@ -4768,7 +4857,32 @@
"filePickerUseThisFolder"
],
"hu": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"id": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"is": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"it": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"collectionActionSetHome",
"setHomeCustomCollection",
"settingsThumbnailShowHdrIcon"
@ -4776,8 +4890,12 @@
"ja": [
"applyTooltip",
"stopTooltip",
"chipActionCreateVault",
"chipActionConfigureVault",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionLock",
"viewerActionUnlock",
"editorActionTransform",
@ -4844,6 +4962,7 @@
"timeMinutes",
"timeDays",
"focalLength",
"stopTooltip",
"chipActionGoToCountryPage",
"chipActionGoToPlacePage",
"chipActionGoToTagPage",
@ -4894,6 +5013,9 @@
"videoActionSkip10",
"videoActionSelectStreams",
"videoActionSetSpeed",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionSettings",
"viewerActionLock",
"viewerActionUnlock",
@ -5491,16 +5613,27 @@
"filePickerUseThisFolder"
],
"ko": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"lt": [
"columnCount",
"saveCopyButtonLabel",
"applyTooltip",
"stopTooltip",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",
"chipActionCreateVault",
"chipActionConfigureVault",
"entryActionCast",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionLock",
"viewerActionUnlock",
"editorActionTransform",
@ -5613,6 +5746,7 @@
"actionRemove",
"resetTooltip",
"saveTooltip",
"stopTooltip",
"pickTooltip",
"doubleBackExitMessage",
"doNotAskAgain",
@ -5672,6 +5806,9 @@
"videoActionSkip10",
"videoActionSelectStreams",
"videoActionSetSpeed",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionSettings",
"viewerActionLock",
"viewerActionUnlock",
@ -6270,7 +6407,11 @@
],
"my": [
"stopTooltip",
"entryActionCast",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"accessibilityAnimationsRemove",
"accessibilityAnimationsKeep",
"overlayHistogramLuminance",
@ -6385,7 +6526,11 @@
],
"nb": [
"stopTooltip",
"entryActionCast",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionLock",
"viewerActionUnlock",
"editorActionTransform",
@ -6412,12 +6557,16 @@
"nl": [
"columnCount",
"stopTooltip",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",
"entryActionShareImageOnly",
"entryActionShareVideoOnly",
"entryActionCast",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionLock",
"viewerActionUnlock",
"editorActionTransform",
@ -6500,8 +6649,12 @@
],
"nn": [
"stopTooltip",
"sourceStateCataloguing",
"entryActionCast",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"accessibilityAnimationsKeep",
"overlayHistogramNone",
"overlayHistogramRGB",
@ -6566,6 +6719,7 @@
"actionRemove",
"resetTooltip",
"saveTooltip",
"stopTooltip",
"pickTooltip",
"doubleBackExitMessage",
"doNotAskAgain",
@ -6621,6 +6775,9 @@
"videoActionSkip10",
"videoActionSelectStreams",
"videoActionSetSpeed",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionSettings",
"viewerActionLock",
"viewerActionUnlock",
@ -7154,12 +7311,37 @@
"filePickerUseThisFolder"
],
"pl": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"pt": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"collectionActionSetHome",
"setHomeCustomCollection",
"settingsThumbnailShowHdrIcon"
],
"ro": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"ru": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"sat": [
"welcomeOptional",
"welcomeTermsToggle",
@ -7187,6 +7369,7 @@
"actionRemove",
"resetTooltip",
"saveTooltip",
"stopTooltip",
"pickTooltip",
"doubleBackExitMessage",
"doNotAskAgain",
@ -7246,6 +7429,9 @@
"videoActionSkip10",
"videoActionSelectStreams",
"videoActionSetSpeed",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionSettings",
"viewerActionLock",
"viewerActionUnlock",
@ -7843,6 +8029,13 @@
"filePickerUseThisFolder"
],
"sk": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd"
],
"sl": [
"itemCount",
"columnCount",
@ -7868,6 +8061,7 @@
"actionRemove",
"resetTooltip",
"saveTooltip",
"stopTooltip",
"pickTooltip",
"doubleBackExitMessage",
"doNotAskAgain",
@ -7927,6 +8121,9 @@
"videoActionSkip10",
"videoActionSelectStreams",
"videoActionSetSpeed",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionSettings",
"viewerActionLock",
"viewerActionUnlock",
@ -8525,6 +8722,10 @@
],
"sv": [
"stopTooltip",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"widgetOpenPageViewer",
"rootDirectoryDescription",
"restrictedAccessDialogMessage",
@ -8841,12 +9042,16 @@
"applyButtonLabel",
"saveCopyButtonLabel",
"applyTooltip",
"stopTooltip",
"chipActionGoToPlacePage",
"chipActionLock",
"chipActionShowCountryStates",
"chipActionCreateVault",
"chipActionConfigureVault",
"entryActionCast",
"videoActionABRepeat",
"videoRepeatActionSetStart",
"videoRepeatActionSetEnd",
"viewerActionLock",
"viewerActionUnlock",
"editorActionTransform",
@ -9234,5 +9439,40 @@
"filePickerOpenFrom",
"filePickerNoItems",
"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"
]
}