I am using better_player and flutter_cast_video packages.
When I play a video, the chromecast button is created, if I click on the screen so that it and the other controls disappear and I press again so that they appear, the chromecast button is created again. Along with this creation, a loading screen of the same Chromecast appears for a few milliseconds.
I need to solve that problem, I have searched the internal code of the flutter_cast_video package and I can’t find anything that solves it either.
Here I leave the respective code for the screen that plays the video
class VideoPlayerScreenValue extends State<VideoPlayerScreen> {
BetterPlayerController? _betterPlayerPlusController;
bool areButtonsVisible = true;
ValueNotifier<bool> areControlsVisible = ValueNotifier<bool>(true);
ValueNotifier<bool> isPlayingNotifier = ValueNotifier<bool>(true);
ValueNotifier<bool> controllerActive = ValueNotifier<bool>(false);
ValueNotifier<Duration> actualSecond = ValueNotifier<Duration>(Duration.zero);
ChromeCastController? _controller;
// Este ValueNotifier mantendrá el estado de reproducción del video
ValueNotifier<bool> isPlaying = ValueNotifier<bool>(true);
// Estas funciones se llamarán cuando se presionen los botones de salto
void seekForward() {
if (controllerActive.value) {
_controller!.seek(interval: 10, relative: true);
} else {
_betterPlayerPlusController!.seekTo(
_betterPlayerPlusController!.videoPlayerController!.value.position +
const Duration(seconds: 10));
}
}
void seekBackward() {
if (controllerActive.value) {
_controller!.seek(interval: -10, relative: true);
} else {
_betterPlayerPlusController!.seekTo(
_betterPlayerPlusController!.videoPlayerController!.value.position -
const Duration(seconds: 10));
}
}
//Initialize the player
@override
void initState() {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
if (widget.videoUrl.isNotEmpty) {
super.initState();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.landscapeRight, DeviceOrientation.landscapeLeft]);
_betterPlayerPlusController = BetterPlayerController(
_betterPlayerConfiguration(),
);
initPlayer();
}
}
@override
Widget build(BuildContext context) {
if (widget.videoUrl.isEmpty) {
return const Center(
child: Text(
'No video available',
style: TextStyle(color: Colors.white),
),
);
}
return _vpControls();
}
String _formatDuration(Duration duration) {
final minutes = duration.inMinutes.toString().padLeft(2, '0');
final seconds = (duration.inSeconds % 60).toString().padLeft(2, '0');
return '$minutes:$seconds';
}
Widget _vpControls() {
return Material(
type: MaterialType.transparency,
child: ValueListenableBuilder<bool>(
valueListenable: areControlsVisible,
builder: (context, isVisible, child) {
return Stack(children: [
GestureDetector(
onTap: _toggleControls,
child: BetterPlayer(
controller: _betterPlayerPlusController!,
),
),
if (isVisible) ...[
Positioned(
top: 25,
left: 0,
child: ValueListenableBuilder<bool>(
valueListenable: isPlayingNotifier,
builder: (context, value, child) {
return Visibility(
visible: value,
child: IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: () {
Navigator.of(context).pop();
},
),
);
},
),
),
Positioned(
bottom: 45,
right: 0,
child: ValueListenableBuilder<bool>(
valueListenable: areControlsVisible,
builder: (context, isVisible, child) {
return Visibility(
visible: isVisible,
child: Padding(
padding: const EdgeInsets.only(bottom: 20.0, right: 10),
child: SizedBox(
width:
50, // Ajusta estos valores al tamaño que desees
height: 50,
child: _chromeCastButton(),
),
),
);
},
),
),
Positioned(
left: 50,
top: MediaQuery.of(context).size.height * 0.45,
child: IconButton(
icon: const Icon(
Icons.fast_rewind,
color: Colors.white,
size: 33,
),
onPressed: seekBackward,
),
),
Positioned(
right: 50,
top: MediaQuery.of(context).size.height * 0.45,
child: IconButton(
icon: const Icon(
Icons.fast_forward,
color: Colors.white,
size: 33,
),
onPressed: seekForward,
),
),
Positioned(
top: MediaQuery.of(context).size.height * 0.45,
right: MediaQuery.of(context).size.width * 0.5,
child: ValueListenableBuilder<bool>(
valueListenable: isPlaying,
builder: (context, value, child) {
return IconButton(
icon: value
? const Icon(
Icons.pause,
color: Colors.white,
size: 33,
)
: const Icon(
Icons.play_arrow,
color: Colors.white,
size: 33,
),
onPressed: () {
if (value) {
if (controllerActive.value) {
_controller!.pause();
} else {
_betterPlayerPlusController!.pause();
}
} else {
if (controllerActive.value) {
_controller!.play();
} else {
_betterPlayerPlusController!.play();
}
}
// Aquí cambiamos el valor de isPlaying cuando se presiona el botón
isPlaying.value = !value;
},
);
},
),
),
Positioned(
bottom: 33,
left: 0,
right: 0,
child: ValueListenableBuilder<Duration>(
valueListenable: actualSecond,
builder: (context, value, child) {
final totalDuration = _betterPlayerPlusController!
.videoPlayerController!.value.duration ??
Duration.zero;
return Slider(
value: value.inSeconds.toDouble(),
min: 0.0,
max: totalDuration.inSeconds.toDouble(),
onChanged: (double newValue) {
if (controllerActive.value) {
print("Tiempo 1");
_controller!
.seek(interval: newValue, relative: false);
} else {
print(2);
_betterPlayerPlusController!
.seekTo(Duration(seconds: newValue.toInt()));
actualSecond.value =
Duration(seconds: newValue.toInt());
}
},
);
},
),
),
Positioned(
bottom: 10,
left: 0,
right: 0,
child: ValueListenableBuilder<Duration>(
valueListenable: actualSecond,
builder: (context, value, child) {
final totalDuration = _betterPlayerPlusController!
.videoPlayerController!.value.duration ??
Duration.zero;
return Text(
'${_formatDuration(value)} / ${_formatDuration(totalDuration)}',
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white),
);
},
),
),
]
]);
},
),
);
}
Timer? _hideControlsTimer;
void _toggleControls() {
if (areControlsVisible.value) {
areControlsVisible.value = false;
_hideControlsTimer?.cancel();
} else {
areControlsVisible.value = true;
_hideControlsTimer = Timer(const Duration(seconds: 10), () {
if (areControlsVisible.value) {
areControlsVisible.value = false;
}
});
}
}
//Configuration of Better Player
BetterPlayerConfiguration _betterPlayerConfiguration() {
return BetterPlayerConfiguration(
autoDispose: true,
startAt: widget.startAt,
aspectRatio: 16 / 9,
fit: BoxFit.contain,
subtitlesConfiguration: _betterPlayerSubtitlesConfiguration(),
allowedScreenSleep: false,
autoDetectFullscreenDeviceOrientation: false,
deviceOrientationsOnFullScreen: [
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft
],
errorBuilder: (context, message) {
return Center(
child: Text(
message!,
style: const TextStyle(color: Colors.white),
),
);
},
autoPlay: true,
controlsConfiguration: _betterPlayerControlsConfiguration(),
);
}
//Configuration of Better Player Subtitles
BetterPlayerSubtitlesConfiguration _betterPlayerSubtitlesConfiguration() {
return const BetterPlayerSubtitlesConfiguration(
alignment: Alignment.center,
fontSize: 20,
fontColor: Colors.white,
backgroundColor: Colors.black,
);
}
//Configuration of Better Player Controls Configuration
BetterPlayerControlsConfiguration _betterPlayerControlsConfiguration() {
return const BetterPlayerControlsConfiguration(
//fullscreenEnableIcon: Icons.fullscreen_exit,
enableSkips: false,
showControls: false,
enableOverflowMenu: false,
enablePlayPause: false,
enableFullscreen: false,
enableMute: false,
enableSubtitles: false,
enableProgressBar: false,
textColor: Color.fromARGB(255, 255, 255, 255),
iconsColor: Color.fromARGB(255, 255, 255, 255),
);
}
//Configuration of the dataSource
Future<void> initPlayer() async {
BetterPlayerDataSource betterPlayerPlusDataSource = BetterPlayerDataSource(
BetterPlayerDataSourceType.network,
widget.videoUrl,
);
_betterPlayerPlusController!.setupDataSource(betterPlayerPlusDataSource);
_betterPlayerPlusController!.addEventsListener((event) {
// Close the player when the video is finished
if (event.betterPlayerEventType == BetterPlayerEventType.finished) {
Navigator.of(context).pop();
}
});
// Add a listener to the video player controller
_betterPlayerPlusController!.videoPlayerController!.addListener(() {
// Update the actualSecond value with the current position of the video
actualSecond.value =
_betterPlayerPlusController!.videoPlayerController!.value.position;
});
// Start a timer to hide the controls after 2 seconds
_hideControlsTimer = Timer(const Duration(seconds: 2), () {
if (areControlsVisible.value) {
areControlsVisible.value = false;
}
});
}
//Dispose the player
@override
void dispose() {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: [SystemUiOverlay.top, SystemUiOverlay.bottom]);
if (widget.videoUrl.isNotEmpty) {
saveLastPosition();
}
_betterPlayerPlusController!.dispose();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
_controller?.removeSessionListener();
_hideControlsTimer?.cancel();
super.dispose();
}
//Save the last position of the video
Future<void> saveLastPosition() async {
Dio dio = Dio();
Duration? lastPosition =
_betterPlayerPlusController!.videoPlayerController?.value.position;
int lastSecond = lastPosition!.inSeconds;
String jsonData = jsonEncode({
'content': widget.content,
'resource': widget.id,
'second': lastSecond,
});
await dio.post(
'https://${Environment.KEY_URL}/api/1.0/seconds_view/',
options: Options(
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer ${Environment.DIO_TOKEN}",
},
),
data: jsonData,
);
}
//! ********************* CHROME CAST ********************* !//
//This widget is used to show the chromecast button
Widget _chromeCastButton() {
return ChromeCastButton(
onButtonCreated: (controller) {
if (_controller == null) {
_controller = controller;
_controller!.addSessionListener();
}
},
onSessionStarted: _onSessionStarted,
onRequestCompleted: _onRequestCompleted,
onSessionEnded: onSessionEnded,
onRequestFailed: (String? error) async {
_controller!.endSession();
},
onPlayerStatusUpdated: (int statusCode) {
// Aquí puedes manejar las actualizaciones de estado del reproductor
print('Player status updated: $statusCode');
},
);
}
void onSessionEnded() async {
print("Session ended");
controllerActive.value = false;
_controller!.removeSessionListener();
_controller!.endSession(); // End the session when it's ended
_controller!.addSessionListener();
}
//Cuando se completa la solicitud y aparece el video
void _onRequestCompleted() async {
if (actualSecond.value != Duration.zero) {
_controller!.seek(
interval: actualSecond.value.inSeconds.toDouble(), relative: true);
actualSecond.value = Duration.zero;
}
}
//Antes de que aparezca el video, mientras se carga
void _onSessionStarted() async {
controllerActive.value = true;
_betterPlayerPlusController!.pause();
await _controller!.loadMedia(
widget.videoUrl.isNotEmpty ? widget.videoUrl : "",
title: widget.name,
subtitle: widget.description,
);
actualSecond.value =
_betterPlayerPlusController!.videoPlayerController!.value.position;
// Show a notification when the video is playing on Chromecast
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'your channel id',
'your channel name',
playSound: false,
enableVibration: false,
importance: Importance.max,
priority: Priority.high,
actions: <AndroidNotificationAction>[
AndroidNotificationAction('stop', 'Stop'),
],
);
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0,
'Video is playing on Chromecast',
'Tap Stop to stop the video',
platformChannelSpecifics,
);
}
Timer? _timer;
Duration? position, duration;
//This function is used to monitor the chromecast
Future<void> _monitor() async {
// monitor cast events
Duration dur = await _controller!.duration(),
pos = await _controller!.position();
if (duration == null || duration!.inSeconds != dur.inSeconds) {
setState(() {
duration = dur;
});
}
if (position == null || position!.inSeconds != pos.inSeconds) {
setState(() {
position = pos;
});
}
}
//This function is used to reset the timer
void resetTimer() {
_timer?.cancel();
_timer = null;
}
//This function is used to start the timer
void startTimer() {
if (_timer?.isActive ?? false) {
return;
}
resetTimer();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_monitor();
});
}
I want that loading screen to stop appearing
Cristian is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.