diff --git a/README.md b/README.md
index 1ae3770..5ee50c9 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-
+
diff --git a/lib/app/app_version.dart b/lib/app/app_version.dart
index f847632..3a5f911 100644
--- a/lib/app/app_version.dart
+++ b/lib/app/app_version.dart
@@ -1,3 +1,3 @@
-const appDisplayVersion = '3.1.7';
-const appBuildNumber = 68;
+const appDisplayVersion = '3.1.8';
+const appBuildNumber = 69;
const appReleaseChannel = 'Stable';
diff --git a/lib/app/changelog.dart b/lib/app/changelog.dart
index 9f3be11..f8415c0 100644
--- a/lib/app/changelog.dart
+++ b/lib/app/changelog.dart
@@ -1,4 +1,14 @@
const lunarisChangelog = [
+ LunarisChange(
+ version: '3.1.8',
+ title: 'Безупречный полноэкранный плеер: устранение остановки при развертывании и отзывчивые кнопки управления',
+ bullets: [
+ 'Непрерывное воспроизведение при открытии на весь экран: видео больше не останавливается самопроизвольно при входе в полноэкранный режим или смене ориентации устройства.',
+ 'Корректный статус воспроизведения: устранен сброс кнопки на «Пуск» при переходе в полный экран — видео продолжает воспроизведение без ложных остановок.',
+ 'Мгновенный отклик кнопок «Пуск/Пауза» и перемотки: жесты управления поверхностью (двойной тап, смахивание громкости) перемещены в фоновый слой, исключая перехват и отмену нажатий по кнопкам управления.',
+ 'Синхронизация состояния при сворачивании: плеер точно запоминает состояние паузы/воспроизведения при выходе из полноэкранного режима обратно в пост.',
+ ],
+ ),
LunarisChange(
version: '3.1.7',
title: 'Устранение критического сбоя при открытии видеопоста (SIGSEGV в libmpv)',
diff --git a/lib/features/post/presentation/widgets/post_media_viewer.dart b/lib/features/post/presentation/widgets/post_media_viewer.dart
index a9bf3f5..b534030 100644
--- a/lib/features/post/presentation/widgets/post_media_viewer.dart
+++ b/lib/features/post/presentation/widgets/post_media_viewer.dart
@@ -571,6 +571,7 @@ class _PostMediaViewerState extends State
final player = _player;
final controller = _controller;
if (player == null || controller == null) return;
+ final wasPlaying = player.state.playing;
setState(() => _inFullscreen = true);
if (!context.mounted) return;
final result = await Navigator.of(context, rootNavigator: true)
@@ -580,6 +581,7 @@ class _PostMediaViewerState extends State
pageBuilder: (_, __, ___) => _FullscreenVideoPage(
player: player,
controller: controller,
+ wasPlaying: wasPlaying,
aspectRatio: widget.post.width > 0 && widget.post.height > 0
? widget.post.width / widget.post.height
: 16 / 9,
@@ -603,6 +605,11 @@ class _PostMediaViewerState extends State
if (mounted) setState(() => _inFullscreen = false);
if (result != null) {
await _applyPlaybackSnapshot(result);
+ if (result.playing && !player.state.playing) {
+ await player.play();
+ } else if (!result.playing && player.state.playing) {
+ await player.pause();
+ }
return;
}
}
@@ -729,6 +736,7 @@ class _FullscreenVideoPage extends StatefulWidget {
const _FullscreenVideoPage({
required this.player,
required this.controller,
+ required this.wasPlaying,
required this.aspectRatio,
required this.loopVideo,
required this.muted,
@@ -742,6 +750,7 @@ class _FullscreenVideoPage extends StatefulWidget {
final Player player;
final VideoController controller;
+ final bool wasPlaying;
final double aspectRatio;
final bool loopVideo;
final bool muted;
@@ -779,6 +788,13 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
_applyOrientation();
_scheduleControlsHide();
+ if (widget.wasPlaying) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ if (mounted && !widget.player.state.playing) {
+ widget.player.play();
+ }
+ });
+ }
}
void _applyOrientation() {
@@ -1131,6 +1147,7 @@ class _VideoSurfaceState extends State<_VideoSurface> {
@override
Widget build(BuildContext context) {
final player = widget.player;
+ Offset? lastTapDown;
final child = LayoutBuilder(
builder: (context, constraints) {
@@ -1143,6 +1160,32 @@ class _VideoSurfaceState extends State<_VideoSurface> {
controller: widget.controller,
fit: widget.coverVideo ? BoxFit.cover : BoxFit.contain,
controls: null,
+ pauseUponEnteringBackgroundMode: false,
+ resumeUponEnteringForegroundMode: false,
+ ),
+ ),
+ Positioned.fill(
+ child: GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTapDown: (details) => lastTapDown = details.localPosition,
+ onTap: widget.onInteract,
+ onDoubleTap: () {
+ if (lastTapDown != null) {
+ _onDoubleTapAt(
+ lastTapDown!,
+ MediaQuery.sizeOf(context).width,
+ );
+ }
+ },
+ onLongPressStart: (_) => _startSpeedBoost(),
+ onLongPressEnd: (_) => _stopSpeedBoost(),
+ onVerticalDragUpdate: (details) {
+ final x = details.localPosition.dx;
+ final width = MediaQuery.sizeOf(context).width;
+ if (x > width * 0.4) {
+ _adjustVolume(details.primaryDelta ?? 0.0);
+ }
+ },
),
),
StreamBuilder(
@@ -1199,36 +1242,16 @@ class _VideoSurfaceState extends State<_VideoSurface> {
},
);
- Offset? lastTapDown;
-
return MouseRegion(
onHover: (_) => widget.onInteract(),
- child: GestureDetector(
- onTapDown: (details) => lastTapDown = details.localPosition,
- onTap: widget.onInteract,
- onDoubleTap: () {
- if (lastTapDown != null) {
- _onDoubleTapAt(lastTapDown!, MediaQuery.sizeOf(context).width);
- }
- },
- onLongPressStart: (_) => _startSpeedBoost(),
- onLongPressEnd: (_) => _stopSpeedBoost(),
- onVerticalDragUpdate: (details) {
- final x = details.localPosition.dx;
- final width = MediaQuery.sizeOf(context).width;
- if (x > width * 0.4) {
- _adjustVolume(details.primaryDelta ?? 0.0);
- }
- },
- child: ClipRRect(
- borderRadius: BorderRadius.circular(widget.fullscreen ? 0 : 10),
- child: widget.fullscreen
- ? SizedBox.expand(child: child)
- : AspectRatio(
- aspectRatio: widget.aspectRatio.clamp(0.35, 2.4),
- child: child,
- ),
- ),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(widget.fullscreen ? 0 : 10),
+ child: widget.fullscreen
+ ? SizedBox.expand(child: child)
+ : AspectRatio(
+ aspectRatio: widget.aspectRatio.clamp(0.35, 2.4),
+ child: child,
+ ),
),
);
}
@@ -1546,32 +1569,37 @@ class _RoundControlButton extends StatelessWidget {
: Colors.white;
return Tooltip(
message: tooltip,
- child: GestureDetector(
- behavior: HitTestBehavior.opaque,
- onTap: onPressed,
- child: AnimatedContainer(
- duration: AppMotion.duration(context, 140),
- width: size,
- height: size,
- decoration: BoxDecoration(
- color: background,
- shape: BoxShape.circle,
- border: Border.all(
- color: selected
- ? scheme.primary.withValues(alpha: 0.4)
- : Colors.white.withValues(alpha: 0.14),
+ child: Material(
+ color: Colors.transparent,
+ shape: const CircleBorder(),
+ child: InkResponse(
+ containedInkWell: true,
+ highlightShape: BoxShape.circle,
+ onTap: onPressed,
+ child: AnimatedContainer(
+ duration: AppMotion.duration(context, 140),
+ width: size,
+ height: size,
+ decoration: BoxDecoration(
+ color: background,
+ shape: BoxShape.circle,
+ border: Border.all(
+ color: selected
+ ? scheme.primary.withValues(alpha: 0.4)
+ : Colors.white.withValues(alpha: 0.14),
+ ),
+ boxShadow: [
+ if (emphasized)
+ BoxShadow(
+ color: scheme.primary.withValues(alpha: 0.45),
+ blurRadius: 28,
+ spreadRadius: 2,
+ ),
+ ],
+ ),
+ child: Center(
+ child: Icon(icon, color: foreground, size: iconSize),
),
- boxShadow: [
- if (emphasized)
- BoxShadow(
- color: scheme.primary.withValues(alpha: 0.45),
- blurRadius: 28,
- spreadRadius: 2,
- ),
- ],
- ),
- child: Center(
- child: Icon(icon, color: foreground, size: iconSize),
),
),
),
@@ -1982,11 +2010,13 @@ class _VideoControls extends StatelessWidget {
emphasized: true,
onPressed: () async {
try {
- if (player.state.completed) {
- await player.seek(Duration.zero);
- await player.play();
+ if (player.state.playing) {
+ await player.pause();
} else {
- await player.playOrPause();
+ if (player.state.completed) {
+ await player.seek(Duration.zero);
+ }
+ await player.play();
}
} catch (_) {
if (player.state.playing) {
diff --git a/lib/shared/widgets/post_card.dart b/lib/shared/widgets/post_card.dart
index 12e1f32..2e979f1 100644
--- a/lib/shared/widgets/post_card.dart
+++ b/lib/shared/widgets/post_card.dart
@@ -596,6 +596,8 @@ class _FeedVideoPreviewState extends State<_FeedVideoPreview> {
controller: _controller,
fit: BoxFit.cover,
controls: null,
+ pauseUponEnteringBackgroundMode: false,
+ resumeUponEnteringForegroundMode: false,
),
),
),
diff --git a/pubspec.yaml b/pubspec.yaml
index ecb8c24..990e03b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: gel_rule_app
description: Local backend/core layer for a Flutter booru browser app.
publish_to: "none"
-version: 3.1.7+68
+version: 3.1.8+69
environment:
sdk: ">=3.3.0 <4.0.0"