As recently Flutterflow has been having issues running youtube videos with their default video player widget. Numerous users have been reporting error 15 and I was facing the same in one of my app, so decided to build this custom youtube player widget.
This widget depends on following pub dependency: youtube_player_flutter ^9.1.3
Widget Code:
// Automatic FlutterFlow imports
import '/backend/backend.dart';
import '/backend/schema/structs/index.dart';
import '/backend/schema/enums/enums.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/custom_code/actions/index.dart'; // Imports custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class YouTubePlayerWidget extends StatefulWidget {
const YouTubePlayerWidget({
super.key,
this.width,
this.height,
required this.videoUrl,
this.autoPlay = false,
this.showControls = true,
});
final double? width;
final double? height;
final String videoUrl;
final bool? autoPlay;
final bool? showControls;
@override
State<YouTubePlayerWidget> createState() => _YouTubePlayerWidgetState();
}
class _YouTubePlayerWidgetState extends State<YouTubePlayerWidget> {
late YoutubePlayerController _controller;
bool _isPlayerReady = false;
String? _errorMessage;
@override
void initState() {
super.initState();
_initializePlayer();
}
void _initializePlayer() {
// Extract video ID from URL using the official method
final videoId = YoutubePlayer.convertUrlToId(widget.videoUrl);
if (videoId != null && videoId.isNotEmpty) {
_controller = YoutubePlayerController(
initialVideoId: videoId,
flags: YoutubePlayerFlags(
autoPlay: widget.autoPlay ?? false,
mute: false,
enableCaption: true,
controlsVisibleAtStart: widget.showControls ?? true,
hideControls: false,
disableDragSeek: false,
loop: false,
forceHD: false,
),
);
} else {
setState(() {
_errorMessage = 'Invalid YouTube URL';
});
}
}
void _listener() {
if (mounted && _isPlayerReady) {
setState(() {});
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_errorMessage != null) {
return Container(
width: widget.width,
height: widget.height,
color: Colors.black,
child: Center(
child: Text(
_errorMessage!,
style: const TextStyle(color: Colors.white),
),
),
);
}
return SizedBox(
width: widget.width,
height: widget.height,
child: YoutubePlayerBuilder(
player: YoutubePlayer(
controller: _controller,
showVideoProgressIndicator: true,
progressIndicatorColor: Colors.red,
progressColors: const ProgressBarColors(
playedColor: Colors.red,
handleColor: Colors.redAccent,
),
onReady: () {
_isPlayerReady = true;
controller.addListener(listener);
},
onEnded: (data) {
// Video ended
},
bottomActions: [
const SizedBox(width: 14.0),
CurrentPosition(),
const SizedBox(width: 8.0),
ProgressBar(
isExpanded: true,
colors: const ProgressBarColors(
playedColor: Colors.red,
handleColor: Colors.redAccent,
),
),
RemainingDuration(),
const SizedBox(width: 8.0),
FullScreenButton(),
],
),
builder: (context, player) {
return player;
},
),
);
}
}