Initial commit
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../core/contracts/playback.dart';
|
||||
import '../../../core/infra/player_engine/player_engine.dart';
|
||||
import '../../../providers/subtitle_settings_provider.dart';
|
||||
|
||||
|
||||
class StyledSubtitleOverlay extends StatefulWidget {
|
||||
final PlayerEngine player;
|
||||
final SubtitleStyleSettings settings;
|
||||
|
||||
|
||||
final ValueListenable<List<String>>? externalLines;
|
||||
|
||||
|
||||
final ValueListenable<EmbySubtitleTrack?>? externalActive;
|
||||
|
||||
const StyledSubtitleOverlay({
|
||||
super.key,
|
||||
required this.player,
|
||||
required this.settings,
|
||||
this.externalLines,
|
||||
this.externalActive,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StyledSubtitleOverlay> createState() => _StyledSubtitleOverlayState();
|
||||
}
|
||||
|
||||
class _StyledSubtitleOverlayState extends State<StyledSubtitleOverlay> {
|
||||
static const _mpvReferenceHeight = 720.0;
|
||||
static const _baseEdgeInset = 24.0;
|
||||
|
||||
List<String> _subtitle = const [];
|
||||
StreamSubscription<List<String>>? _subscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bindPlayer(widget.player);
|
||||
widget.externalLines?.addListener(_onExternalChanged);
|
||||
widget.externalActive?.addListener(_onExternalChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant StyledSubtitleOverlay oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.player != widget.player) {
|
||||
unawaited(_subscription?.cancel());
|
||||
_bindPlayer(widget.player);
|
||||
}
|
||||
if (oldWidget.externalLines != widget.externalLines) {
|
||||
oldWidget.externalLines?.removeListener(_onExternalChanged);
|
||||
widget.externalLines?.addListener(_onExternalChanged);
|
||||
}
|
||||
if (oldWidget.externalActive != widget.externalActive) {
|
||||
oldWidget.externalActive?.removeListener(_onExternalChanged);
|
||||
widget.externalActive?.addListener(_onExternalChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unawaited(_subscription?.cancel());
|
||||
widget.externalLines?.removeListener(_onExternalChanged);
|
||||
widget.externalActive?.removeListener(_onExternalChanged);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _bindPlayer(PlayerEngine player) {
|
||||
_subtitle = player.state.subtitle;
|
||||
_subscription = player.stream.subtitle.listen((value) {
|
||||
if (mounted) setState(() => _subtitle = value);
|
||||
});
|
||||
}
|
||||
|
||||
void _onExternalChanged() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
bool get _externalIsActive => widget.externalActive?.value != null;
|
||||
|
||||
List<String> get _activeLines =>
|
||||
_externalIsActive ? (widget.externalLines?.value ?? const []) : _subtitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final primaryText = _joinLines(_activeLines);
|
||||
if (primaryText.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return IgnorePointer(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth;
|
||||
final height = constraints.maxHeight;
|
||||
if (!width.isFinite ||
|
||||
!height.isFinite ||
|
||||
width <= 0 ||
|
||||
height <= 0) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final fontSize = _scaleFontSize(widget.settings.fontSize, height);
|
||||
|
||||
final position = widget.settings.position.clamp(0, 150).toDouble();
|
||||
final verticalProgress = math.min(position, 100.0) / 100.0;
|
||||
final extraLowering = math.max(0.0, position - 100.0) / 50.0;
|
||||
final bottomInset =
|
||||
_baseEdgeInset * (1.0 - extraLowering.clamp(0.0, 1.0));
|
||||
|
||||
final lineCount = math.max(1, primaryText.split('\n').length);
|
||||
final estimatedBlockHeight = math.min(
|
||||
height * 0.5,
|
||||
lineCount * fontSize * 1.32 + 12.0,
|
||||
);
|
||||
|
||||
final verticalRange = math.max(
|
||||
0.0,
|
||||
height - _baseEdgeInset - bottomInset - estimatedBlockHeight,
|
||||
);
|
||||
final top = _baseEdgeInset + verticalRange * verticalProgress;
|
||||
final horizontalInset = math.min(_baseEdgeInset, width * 0.06);
|
||||
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Positioned(
|
||||
left: horizontalInset,
|
||||
right: horizontalInset,
|
||||
top: top,
|
||||
child: _buildLine(
|
||||
text: primaryText,
|
||||
fontSize: fontSize,
|
||||
bgOpacity: widget.settings.bgOpacity.clamp(0.0, 1.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _joinLines(List<String> lines) => lines
|
||||
.map((line) => line.trim())
|
||||
.where((line) => line.isNotEmpty)
|
||||
.join('\n');
|
||||
|
||||
double _scaleFontSize(double settingsFontSize, double height) =>
|
||||
(settingsFontSize * height / _mpvReferenceHeight).clamp(8.0, 200.0);
|
||||
|
||||
Widget _buildLine({
|
||||
required String text,
|
||||
required double fontSize,
|
||||
required double bgOpacity,
|
||||
}) {
|
||||
final child = Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
softWrap: true,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: fontSize,
|
||||
height: 1.32,
|
||||
letterSpacing: 0.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
shadows: const [
|
||||
Shadow(color: Colors.black, blurRadius: 2, offset: Offset(0, 1)),
|
||||
Shadow(color: Colors.black87, blurRadius: 4),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (bgOpacity > 0) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(bgOpacity),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user