feat: video fill modes
This commit is contained in:
@@ -53,6 +53,10 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
StreamSubscription<PlayerLog>? _logSubscription;
|
||||
StreamSubscription<String>? _errorSubscription;
|
||||
|
||||
// BoxFit mode state: 0=contain (letterbox), 1=cover (fill screen), 2=fill (stretch)
|
||||
int _boxFitMode = 0;
|
||||
bool _isPinching = false; // Track if a pinch gesture is occurring
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -287,6 +291,38 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cycle through BoxFit modes: contain → cover → fill → contain (for button)
|
||||
void _cycleBoxFitMode() {
|
||||
setState(() {
|
||||
_boxFitMode = (_boxFitMode + 1) % 3;
|
||||
});
|
||||
final modes = ['contain (letterbox)', 'cover (fill screen)', 'fill (stretch)'];
|
||||
appLogger.d('BoxFit mode: ${modes[_boxFitMode]}');
|
||||
}
|
||||
|
||||
/// Toggle between contain and cover modes only (for pinch gesture)
|
||||
void _toggleContainCover() {
|
||||
setState(() {
|
||||
_boxFitMode = _boxFitMode == 0 ? 1 : 0; // Toggle between 0 and 1
|
||||
});
|
||||
final modes = ['contain (letterbox)', 'cover (fill screen)'];
|
||||
appLogger.d('BoxFit mode toggled: ${modes[_boxFitMode]}');
|
||||
}
|
||||
|
||||
/// Get current BoxFit based on mode
|
||||
BoxFit get _getCurrentBoxFit {
|
||||
switch (_boxFitMode) {
|
||||
case 0:
|
||||
return BoxFit.contain;
|
||||
case 1:
|
||||
return BoxFit.cover;
|
||||
case 2:
|
||||
return BoxFit.fill;
|
||||
default:
|
||||
return BoxFit.contain;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Stop progress tracking
|
||||
@@ -1022,22 +1058,47 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
children: [
|
||||
// Video player
|
||||
Center(
|
||||
child: Video(
|
||||
controller: controller!,
|
||||
controls: (state) => plexVideoControlsBuilder(
|
||||
player!,
|
||||
widget.metadata,
|
||||
onNext: _nextEpisode != null ? _playNext : null,
|
||||
onPrevious: _previousEpisode != null ? _playPrevious : null,
|
||||
availableVersions: _availableVersions,
|
||||
selectedMediaIndex: widget.selectedMediaIndex,
|
||||
body: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent, // Allow taps to pass through to controls
|
||||
onScaleStart: (details) {
|
||||
// Initialize pinch gesture tracking (mobile only)
|
||||
if (!PlatformDetector.isMobile(context)) return;
|
||||
_isPinching = false;
|
||||
},
|
||||
onScaleUpdate: (details) {
|
||||
// Track if this is a pinch gesture (2+ fingers) on mobile
|
||||
if (!PlatformDetector.isMobile(context)) return;
|
||||
if (details.pointerCount >= 2) {
|
||||
_isPinching = true;
|
||||
}
|
||||
},
|
||||
onScaleEnd: (details) {
|
||||
// Only toggle if we detected a pinch gesture on mobile
|
||||
if (!PlatformDetector.isMobile(context)) return;
|
||||
if (_isPinching) {
|
||||
_toggleContainCover();
|
||||
_isPinching = false;
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
// Video player
|
||||
Center(
|
||||
child: Video(
|
||||
controller: controller!,
|
||||
fit: _getCurrentBoxFit,
|
||||
controls: (state) => plexVideoControlsBuilder(
|
||||
player!,
|
||||
widget.metadata,
|
||||
onNext: _nextEpisode != null ? _playNext : null,
|
||||
onPrevious: _previousEpisode != null ? _playPrevious : null,
|
||||
availableVersions: _availableVersions,
|
||||
selectedMediaIndex: widget.selectedMediaIndex,
|
||||
boxFitMode: _boxFitMode,
|
||||
onCycleBoxFitMode: _cycleBoxFitMode,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Play Next Dialog
|
||||
if (_showPlayNextDialog && _nextEpisode != null)
|
||||
Positioned.fill(
|
||||
@@ -1149,7 +1210,8 @@ class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -26,6 +26,8 @@ Widget plexVideoControlsBuilder(
|
||||
VoidCallback? onPrevious,
|
||||
List<PlexMediaVersion>? availableVersions,
|
||||
int? selectedMediaIndex,
|
||||
int boxFitMode = 0,
|
||||
VoidCallback? onCycleBoxFitMode,
|
||||
}) {
|
||||
return PlexVideoControls(
|
||||
player: player,
|
||||
@@ -34,6 +36,8 @@ Widget plexVideoControlsBuilder(
|
||||
onPrevious: onPrevious,
|
||||
availableVersions: availableVersions ?? [],
|
||||
selectedMediaIndex: selectedMediaIndex ?? 0,
|
||||
boxFitMode: boxFitMode,
|
||||
onCycleBoxFitMode: onCycleBoxFitMode,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +48,8 @@ class PlexVideoControls extends StatefulWidget {
|
||||
final VoidCallback? onPrevious;
|
||||
final List<PlexMediaVersion> availableVersions;
|
||||
final int selectedMediaIndex;
|
||||
final int boxFitMode;
|
||||
final VoidCallback? onCycleBoxFitMode;
|
||||
|
||||
const PlexVideoControls({
|
||||
super.key,
|
||||
@@ -53,6 +59,8 @@ class PlexVideoControls extends StatefulWidget {
|
||||
this.onPrevious,
|
||||
this.availableVersions = const [],
|
||||
this.selectedMediaIndex = 0,
|
||||
this.boxFitMode = 0,
|
||||
this.onCycleBoxFitMode,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -271,6 +279,32 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
return subtitles.isNotEmpty;
|
||||
}
|
||||
|
||||
IconData _getBoxFitIcon(int mode) {
|
||||
switch (mode) {
|
||||
case 0:
|
||||
return Icons.crop_free; // contain (letterbox)
|
||||
case 1:
|
||||
return Icons.crop; // cover (fill screen)
|
||||
case 2:
|
||||
return Icons.fit_screen; // fill (stretch)
|
||||
default:
|
||||
return Icons.crop_free;
|
||||
}
|
||||
}
|
||||
|
||||
String _getBoxFitTooltip(int mode) {
|
||||
switch (mode) {
|
||||
case 0:
|
||||
return 'Letterbox';
|
||||
case 1:
|
||||
return 'Fill screen';
|
||||
case 2:
|
||||
return 'Stretch';
|
||||
default:
|
||||
return 'Letterbox';
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTrackAndChapterControls() {
|
||||
return StreamBuilder<Tracks>(
|
||||
stream: widget.player.stream.tracks,
|
||||
@@ -304,6 +338,16 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
||||
icon: const Icon(Icons.video_file, color: Colors.white),
|
||||
onPressed: _showVersionBottomSheet,
|
||||
),
|
||||
// BoxFit mode cycle button
|
||||
if (widget.onCycleBoxFitMode != null)
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_getBoxFitIcon(widget.boxFitMode),
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: _getBoxFitTooltip(widget.boxFitMode),
|
||||
onPressed: widget.onCycleBoxFitMode,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user