fix: fullscreen play-pause button, search clear providers, video volume persistence & codec fallback (v3.1.6)
Build and Release / Build Windows x64 (push) Has been cancelled
Build and Release / Build Windows x64 (push) Has been cancelled
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/RarDog/Lunaris/releases"><img src="https://img.shields.io/badge/Version-3.1.5-8A2BE2?style=for-the-badge&logo=semver&logoColor=white" alt="Version 3.1.5" /></a>
|
<a href="https://github.com/RarDog/Lunaris/releases"><img src="https://img.shields.io/badge/Version-3.1.6-8A2BE2?style=for-the-badge&logo=semver&logoColor=white" alt="Version 3.1.6" /></a>
|
||||||
<img src="https://img.shields.io/badge/Flutter-3.47+-02569B?style=for-the-badge&logo=flutter&logoColor=white" alt="Flutter" />
|
<img src="https://img.shields.io/badge/Flutter-3.47+-02569B?style=for-the-badge&logo=flutter&logoColor=white" alt="Flutter" />
|
||||||
<img src="https://img.shields.io/badge/Platform-Android%20%7C%20Linux%20%7C%20Windows-00C853?style=for-the-badge" alt="Platforms" />
|
<img src="https://img.shields.io/badge/Platform-Android%20%7C%20Linux%20%7C%20Windows-00C853?style=for-the-badge" alt="Platforms" />
|
||||||
<a href="https://github.com/RarDog/Lunaris/wiki"><img src="https://img.shields.io/badge/Wiki-Documentation-FF6F00?style=for-the-badge&logo=gitbook&logoColor=white" alt="Wiki" /></a>
|
<a href="https://github.com/RarDog/Lunaris/wiki"><img src="https://img.shields.io/badge/Wiki-Documentation-FF6F00?style=for-the-badge&logo=gitbook&logoColor=white" alt="Wiki" /></a>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
const appDisplayVersion = '3.1.5';
|
const appDisplayVersion = '3.1.6';
|
||||||
const appBuildNumber = 66;
|
const appBuildNumber = 67;
|
||||||
const appReleaseChannel = 'Stable';
|
const appReleaseChannel = 'Stable';
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
const lunarisChangelog = [
|
const lunarisChangelog = [
|
||||||
|
LunarisChange(
|
||||||
|
version: '3.1.6',
|
||||||
|
title: 'Надежный полноэкранный плеер, сохранение провайдеров в поиске, громкость и устранение ошибки кодека',
|
||||||
|
bullets: [
|
||||||
|
'Полноэкранный плеер — отзывчивая кнопка Пуск/Пауза: центральная кнопка управления воспроизведением полностью переработана на непроницаемый GestureDetector со сквиркл-откликом, гарантируя мгновенное срабатывание без перехвата внешними жестами.',
|
||||||
|
'Сохранение источников при нажатии на крестик в поиске: клик по кнопке сброса в строке поиска теперь очищает только поисковый запрос и теги, никогда не сбрасывая выбранные источники до «Все».',
|
||||||
|
'Безупречное сохранение громкости: громкость видеоплеера корректно инициализируется из настроек, сохраняется при регулировках жестом и переходе между постами, не сбрасываясь на 100% при открытии нового видео и перезапуске приложения.',
|
||||||
|
'Автоматический fallback для ошибки Could not open codec: видеоплеер получил безопасные профили декодирования и автопереключение на программный лавк-декодер (FFmpeg software fallback) при редких сбоях аппаратного ускорения.',
|
||||||
|
],
|
||||||
|
),
|
||||||
LunarisChange(
|
LunarisChange(
|
||||||
version: '3.1.5',
|
version: '3.1.5',
|
||||||
title: 'Сохранение громкости видео, исправление полноэкранного плеера, фильтрации и удобства комментариев',
|
title: 'Сохранение громкости видео, исправление полноэкранного плеера, фильтрации и удобства комментариев',
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class _FeedToolbarState extends State<FeedToolbar> {
|
|||||||
},
|
},
|
||||||
onCleared: () {
|
onCleared: () {
|
||||||
_query = '';
|
_query = '';
|
||||||
widget.onClearFilters();
|
widget.onSearch('');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -235,7 +235,7 @@ class _FeedToolbarState extends State<FeedToolbar> {
|
|||||||
},
|
},
|
||||||
onCleared: () {
|
onCleared: () {
|
||||||
_query = '';
|
_query = '';
|
||||||
widget.onClearFilters();
|
widget.onSearch('');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -115,6 +115,27 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
unawaited(_retryVideo());
|
unawaited(_retryVideo());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldWidget.initialVolume != widget.initialVolume) {
|
||||||
|
_currentVolume = widget.initialVolume;
|
||||||
|
unawaited(_applyVolume());
|
||||||
|
}
|
||||||
|
if (oldWidget.initialMuted != widget.initialMuted) {
|
||||||
|
_muted = widget.initialMuted;
|
||||||
|
unawaited(_applyVolume());
|
||||||
|
}
|
||||||
|
if (oldWidget.initialHalfVolume != widget.initialHalfVolume) {
|
||||||
|
_halfVolume = widget.initialHalfVolume;
|
||||||
|
unawaited(_applyVolume());
|
||||||
|
}
|
||||||
|
if (oldWidget.initialLoop != widget.initialLoop) {
|
||||||
|
_loopVideo = widget.initialLoop;
|
||||||
|
unawaited(_player?.setPlaylistMode(
|
||||||
|
_loopVideo ? PlaylistMode.single : PlaylistMode.none,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (oldWidget.initialCoverVideo != widget.initialCoverVideo) {
|
||||||
|
_coverVideo = widget.initialCoverVideo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -177,6 +198,7 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
onVolumeChanged: (vol) {
|
onVolumeChanged: (vol) {
|
||||||
_currentVolume = vol;
|
_currentVolume = vol;
|
||||||
widget.onVolumeChanged?.call(vol);
|
widget.onVolumeChanged?.call(vol);
|
||||||
|
_emitPlaybackPreferences();
|
||||||
},
|
},
|
||||||
fullscreen: widget.fullscreen,
|
fullscreen: widget.fullscreen,
|
||||||
errorMessage: _videoError,
|
errorMessage: _videoError,
|
||||||
@@ -338,6 +360,12 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
MediaKit.ensureInitialized();
|
MediaKit.ensureInitialized();
|
||||||
_player = Player();
|
_player = Player();
|
||||||
_controller = VideoController(_player!);
|
_controller = VideoController(_player!);
|
||||||
|
try {
|
||||||
|
final native = _player!.platform as dynamic;
|
||||||
|
native?.setProperty('hwdec', 'auto-safe', waitForInitialization: false);
|
||||||
|
native?.setProperty('vd-lavc-software-fallback', 'yes', waitForInitialization: false);
|
||||||
|
native?.setProperty('vd-lavc-check-hw-profile', 'yes', waitForInitialization: false);
|
||||||
|
} catch (_) {}
|
||||||
_applyVolume();
|
_applyVolume();
|
||||||
_player!.setPlaylistMode(
|
_player!.setPlaylistMode(
|
||||||
_loopVideo ? PlaylistMode.single : PlaylistMode.none,
|
_loopVideo ? PlaylistMode.single : PlaylistMode.none,
|
||||||
@@ -381,6 +409,7 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
),
|
),
|
||||||
play: false,
|
play: false,
|
||||||
);
|
);
|
||||||
|
await _applyVolume();
|
||||||
if (initialPosition > Duration.zero) {
|
if (initialPosition > Duration.zero) {
|
||||||
await player.seek(initialPosition);
|
await player.seek(initialPosition);
|
||||||
}
|
}
|
||||||
@@ -396,14 +425,22 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
await _openVideo(play: play);
|
await _openVideo(play: play);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If format error occurred on initial load (often due to async referer headers), retry once automatically
|
final lower = message.toLowerCase();
|
||||||
|
// If format or codec error occurred (e.g. unsupported hwdec profile), fallback to software decoding and retry once
|
||||||
if (!_retriedFormatError &&
|
if (!_retriedFormatError &&
|
||||||
(message.contains('Failed to recognize file format') ||
|
(lower.contains('codec') ||
|
||||||
message.contains('Demuxer error') ||
|
lower.contains('could not open') ||
|
||||||
message.contains('Could not open') ||
|
lower.contains('couldnt open') ||
|
||||||
message.contains('Stream format not recognized') ||
|
lower.contains('could not initialize codec') ||
|
||||||
message.contains('format unrecognized'))) {
|
lower.contains('demuxer error') ||
|
||||||
|
lower.contains('format unrecognized') ||
|
||||||
|
lower.contains('stream format not recognized') ||
|
||||||
|
lower.contains('failed to recognize file format'))) {
|
||||||
_retriedFormatError = true;
|
_retriedFormatError = true;
|
||||||
|
try {
|
||||||
|
final native = _player?.platform as dynamic;
|
||||||
|
await native?.setProperty('hwdec', 'no', waitForInitialization: false);
|
||||||
|
} catch (_) {}
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 300));
|
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||||
if (mounted && _player != null) {
|
if (mounted && _player != null) {
|
||||||
_videoIndex = 0;
|
_videoIndex = 0;
|
||||||
@@ -419,6 +456,10 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
Future<void> _retryVideo() async {
|
Future<void> _retryVideo() async {
|
||||||
_videoIndex = 0;
|
_videoIndex = 0;
|
||||||
_retriedFormatError = false;
|
_retriedFormatError = false;
|
||||||
|
try {
|
||||||
|
final native = _player?.platform as dynamic;
|
||||||
|
await native?.setProperty('hwdec', 'no', waitForInitialization: false);
|
||||||
|
} catch (_) {}
|
||||||
await _openVideo(play: true);
|
await _openVideo(play: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -732,6 +773,7 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
|
|||||||
_loopVideo = widget.loopVideo;
|
_loopVideo = widget.loopVideo;
|
||||||
_halfVolume = widget.halfVolume;
|
_halfVolume = widget.halfVolume;
|
||||||
_currentVolume = widget.initialVolume;
|
_currentVolume = widget.initialVolume;
|
||||||
|
_applyVolume();
|
||||||
_isLandscape = widget.aspectRatio > 1.05;
|
_isLandscape = widget.aspectRatio > 1.05;
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||||
_applyOrientation();
|
_applyOrientation();
|
||||||
@@ -996,9 +1038,17 @@ class _VideoSurfaceState extends State<_VideoSurface> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_currentVolume = widget.player.state.volume > 0
|
_currentVolume = widget.initialVolume;
|
||||||
? widget.player.state.volume
|
widget.player.setVolume(_currentVolume);
|
||||||
: widget.initialVolume;
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant _VideoSurface oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.initialVolume != widget.initialVolume) {
|
||||||
|
_currentVolume = widget.initialVolume;
|
||||||
|
widget.player.setVolume(_currentVolume);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -1495,9 +1545,9 @@ class _RoundControlButton extends StatelessWidget {
|
|||||||
: Colors.white;
|
: Colors.white;
|
||||||
return Tooltip(
|
return Tooltip(
|
||||||
message: tooltip,
|
message: tooltip,
|
||||||
child: InkResponse(
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: onPressed,
|
onTap: onPressed,
|
||||||
radius: size / 2 + 4,
|
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: AppMotion.duration(context, 140),
|
duration: AppMotion.duration(context, 140),
|
||||||
width: size,
|
width: size,
|
||||||
@@ -1513,14 +1563,17 @@ class _RoundControlButton extends StatelessWidget {
|
|||||||
boxShadow: [
|
boxShadow: [
|
||||||
if (emphasized)
|
if (emphasized)
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: scheme.primary.withValues(alpha: 0.35),
|
color: scheme.primary.withValues(alpha: 0.45),
|
||||||
blurRadius: 14,
|
blurRadius: 28,
|
||||||
|
spreadRadius: 2,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
child: Center(
|
||||||
child: Icon(icon, color: foreground, size: iconSize),
|
child: Icon(icon, color: foreground, size: iconSize),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1918,41 +1971,31 @@ class _VideoControls extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 32),
|
const SizedBox(width: 32),
|
||||||
Container(
|
_RoundControlButton(
|
||||||
width: 68,
|
|
||||||
height: 68,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: scheme.primary.withValues(alpha: 0.94),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: scheme.primary.withValues(alpha: 0.45),
|
|
||||||
blurRadius: 28,
|
|
||||||
spreadRadius: 2,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: IconButton(
|
|
||||||
tooltip: playing ? 'Пауза' : 'Воспроизведение',
|
tooltip: playing ? 'Пауза' : 'Воспроизведение',
|
||||||
|
icon: playing
|
||||||
|
? Icons.pause_rounded
|
||||||
|
: Icons.play_arrow_rounded,
|
||||||
|
size: 68,
|
||||||
iconSize: 42,
|
iconSize: 42,
|
||||||
color: scheme.onPrimary,
|
emphasized: true,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
if (player.state.completed) {
|
||||||
|
await player.seek(Duration.zero);
|
||||||
|
await player.play();
|
||||||
|
} else {
|
||||||
|
await player.playOrPause();
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
if (player.state.playing) {
|
if (player.state.playing) {
|
||||||
await player.pause();
|
await player.pause();
|
||||||
} else {
|
} else {
|
||||||
if (player.state.completed) {
|
|
||||||
await player.seek(Duration.zero);
|
|
||||||
}
|
|
||||||
await player.play();
|
await player.play();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
onInteract?.call();
|
onInteract?.call();
|
||||||
},
|
},
|
||||||
icon: Icon(
|
|
||||||
playing
|
|
||||||
? Icons.pause_rounded
|
|
||||||
: Icons.play_arrow_rounded,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 32),
|
const SizedBox(width: 32),
|
||||||
_RoundControlButton(
|
_RoundControlButton(
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
name: gel_rule_app
|
name: gel_rule_app
|
||||||
description: Local backend/core layer for a Flutter booru browser app.
|
description: Local backend/core layer for a Flutter booru browser app.
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 3.1.5+66
|
version: 3.1.6+67
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.3.0 <4.0.0"
|
sdk: ">=3.3.0 <4.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user