Custom UI
FView's panelBuilder decides what sits on top of the video surface. With the fplayer core package the default is the minimal panel. When you want a full player UI, start from the built-in UI, or build the whole thing yourself.
FView parameters
typedef FPanelWidgetBuilder = Widget Function(
FPlayer player,
FData data,
BuildContext context,
Size viewSize,
Rect texturePos,
);
FView({
Key? key,
required FPlayer player,
double? width,
double? height,
FFit fit = FFit.contain,
FFit fsFit = FFit.contain,
FPanelWidgetBuilder panelBuilder = minimalFPanelBuilder,
Color color = const Color(0xFF607D8B),
ImageProvider? cover,
bool fs = true,
bool showSubtitles = true,
void Function(FData)? onDispose,
});
player: the currentFPlayer. The panel listens to changes in itsFValue.data: a data container shared by the fullscreen and inline panels; its contents persist after a panel is destroyed.viewSize: the actual on-screen size ofFView.texturePos: where the video texture sits insideFView. It can extend past theFViewbounds.showSubtitles: whether to show the built-in subtitle layer. Set it tofalsewhen you draw your own.onDispose: called whenFViewis disposed, for restoring system settings or clearing panel state.
FFit.contain, FFit.fill, and FFit.cover control how the video fills its target area. Fullscreen mode uses fsFit; normal mode uses fit.
Custom panel example
The panel below puts a single play/pause button in the bottom left corner of the video:
import 'dart:math';
import 'package:fplayer/fplayer.dart';
import 'package:flutter/material.dart';
class CustomPanel extends StatefulWidget {
const CustomPanel({
super.key,
required this.player,
required this.viewSize,
required this.texturePos,
});
final FPlayer player;
final Size viewSize;
final Rect texturePos;
State<CustomPanel> createState() => _CustomPanelState();
}
class _CustomPanelState extends State<CustomPanel> {
bool _playing = false;
void initState() {
super.initState();
widget.player.addListener(_playerValueChanged);
}
void _playerValueChanged() {
final playing = widget.player.state == FState.started;
if (playing != _playing) {
setState(() => _playing = playing);
}
}
Widget build(BuildContext context) {
final rect = Rect.fromLTRB(
max(0.0, widget.texturePos.left),
max(0.0, widget.texturePos.top),
min(widget.viewSize.width, widget.texturePos.right),
min(widget.viewSize.height, widget.texturePos.bottom),
);
return Positioned.fromRect(
rect: rect,
child: Align(
alignment: Alignment.bottomLeft,
child: IconButton(
color: Colors.white,
icon: Icon(_playing ? Icons.pause : Icons.play_arrow),
onPressed: () {
_playing ? widget.player.pause() : widget.player.start();
},
),
),
);
}
void dispose() {
widget.player.removeListener(_playerValueChanged);
super.dispose();
}
}
Pass it to FView:
FView(
player: player,
panelBuilder: (
FPlayer player,
FData data,
BuildContext context,
Size viewSize,
Rect texturePos,
) {
return CustomPanel(
player: player,
viewSize: viewSize,
texturePos: texturePos,
);
},
)
Notes
- The panel watches player state through
player.addListener; remove the listener when the panel is disposed. - Switching to fullscreen rebuilds the panel, so put anything that must survive the switch in
FData. awaitplayer calls. Avoid callingstartorpausewhile the player is initializing or in theasyncPreparingstate.- If you need the full gestures and settings panel, use
fplayer_uirather than reimplementing the player state machine.
