How to use youtube_player_flutter with ListView builder?

Issue

I’m using the youtube_player_flutter package to play youtube embedded videos. I’m facing problems with the package. I tried almost everything as per my understanding using init method, function to assign the controller, etc none works.

Issues

  1. YoutubeController only take a single id and I have a list of URLs, so I assign the controller in the listview builder method with the videoId assigned as per the index. The issue is when the app launch it works fine, but if I hot reload or save the play button turns into infinite loading, if I hot reload again it stops and turns back to the play button. If I play the video, the player plays but it still shows the thumbnail. If hot reloaded, the thumbnail changes to the playing video. After the initial launch if I hot reload or save. It will always need a hot reload to change the state.

  2. Using onEnd() property to reset the video to the initial state i.e video thumbnail with the play button. But using onEnded: () {_ytController.reset(); } shows the infinite loading with the video thumbnail. PS. states change only after hot reload.

  3. When the video is playing I don’t want to show the headline container so I’m using _isPlaying bool to change the state. The value is changing but the container doesn’t disappear even with the setstate(). I think it needs to rebuild again.

  4. Metadata Title is not displayed but works when the controller is assigned in init()

Code:

import 'package:flutter/material.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';

class HomeScreen extends StatefulWidget {
  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  // YT Controller
  // late YoutubePlayerController _youtubePlayerController;

  // Video Title
  late String videoTitle;

  // Url List
  final List<String> _videoUrlList = [
    'https://youtu.be/dWs3dzj4Wng',
    'https://youtu.be/S3npWREXr8s',
  ];

  /*
  YoutubePlayerController _ytFN({String? url}) {
    return YoutubePlayerController(
      initialVideoId: YoutubePlayer.convertUrlToId(url!)!,
      flags: const YoutubePlayerFlags(
        autoPlay: false,
        enableCaption: true,
      ),
    );
  }

  //
  @override
  void initState() {
    _ytFN(url: _videoUrlList.first);
    super.initState();
  }

  //
  @override
  void dispose() {
    _ytFN().dispose();
    _youtubePlayerController.dispose();
    super.dispose();
  }
*/
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Tubeloid'),
        centerTitle: true,
        actions: [
          IconButton(
            onPressed: () {},
            icon: const Icon(Icons.menu_outlined),
          )
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(10.0),
        child: ListView.builder(
          itemCount: _videoUrlList.length,
          shrinkWrap: true,
          itemBuilder: (context, index) {
            ///--------------------------   ISSUE NO. 1

            // YT Controller
            final _ytController = YoutubePlayerController(
              initialVideoId:
                  YoutubePlayer.convertUrlToId(_videoUrlList[index])!,
              flags: const YoutubePlayerFlags(
                autoPlay: false,
                enableCaption: true,
                captionLanguage: 'en',
              ),
            );

            // for container visibility
            bool _isPlaying = false;

            return Padding(
              padding: const EdgeInsets.symmetric(vertical: 8.0),
              child: Stack(
                alignment: Alignment.bottomCenter,
                children: [
                  // Youtube Player
                  Container(
                    height: 220.0,
                    decoration: const BoxDecoration(
                      color: Color(0xfff5f5f5),
                      borderRadius: BorderRadius.all(Radius.circular(12)),
                    ),
                    child: ClipRRect(
                      borderRadius: const BorderRadius.all(Radius.circular(12)),
                      child: YoutubePlayer(
                        controller: _ytController
                          ..addListener(() {
                            if (_ytController.value.isPlaying) {
                              setState(() {
                                _isPlaying = true;
                              });
                            } else {
                              _isPlaying = false;
                            }
                          }),
                        showVideoProgressIndicator: true,
                        progressIndicatorColor: Colors.lightBlueAccent,
                        bottomActions: [
                          CurrentPosition(),
                          ProgressBar(isExpanded: true),
                          FullScreenButton(),
                        ],
                        onEnded: (YoutubeMetaData _md) {
                          ///---------------------------   ISSUE NO. 2
                          _ytController.reset();
                          // _ytController.seekTo(const Duration(seconds: 1));
                          // _ytController.pause();
                          _md.videoId;
                          print(_md.title);
                        },
                      ),
                    ),
                  ),

                  ///--------------------------   ISSUE NO. 3

                  // Headline
                  _isPlaying
                      ? Container()
                      : Container(
                          width: double.infinity,
                          decoration: BoxDecoration(
                            color: Colors.white.withOpacity(0.9),
                            borderRadius: const BorderRadius.only(
                              bottomRight: Radius.circular(12),
                              bottomLeft: Radius.circular(12),
                            ),
                          ),
                          child: Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Text(
                          ///--------------------------   ISSUE NO. 4
                              _ytController.metadata.title,
                              style: const TextStyle(
                                fontSize: 20.0,
                                color: Colors.black,
                              ),
                            ),
                          ),
                        ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

Solution

Some comments before code)

  1. don’t put this
    final _ytController = YoutubePlayerController…
    into builder – you recreate all controllers every time, when run setstate.
    You have to make some list of controllers for every video and fill it into init override
  2. reset to begin you should do with _ytController.seekTo command.
  3. save you isPlayng states into global list and check it into the builder
  4. I got Metadata title from contrller ONLY when concrete video is playing. So better way – load titles for your videos previously through usual http.get request (to something like such https://noembed.com/embed?url=https://www.youtube.com/watch?v=668nUCeBHyY)

my code:

import 'package:flutter/material.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';

class YT extends StatefulWidget {
  const YT({Key? key}) : super(key: key);

  @override
  _YTState createState() => _YTState();
}

class _YTState extends State<YT> {
  late String videoTitle;
  // Url List
  final List<String> _videoUrlList = [
    'https://youtu.be/dWs3dzj4Wng',
    'https://www.youtube.com/watch?v=668nUCeBHyY',
    'https://youtu.be/S3npWREXr8s',
  ];

  List <YoutubePlayerController> lYTC = [];

  Map<String, dynamic> cStates = {};

  @override
  void initState() {
    super.initState();
    fillYTlists();
  }

  fillYTlists(){
    for (var element in _videoUrlList) {
      String _id = YoutubePlayer.convertUrlToId(element)!;
      YoutubePlayerController _ytController = YoutubePlayerController(
        initialVideoId: _id,
        flags: const YoutubePlayerFlags(
          autoPlay: false,
          enableCaption: true,
          captionLanguage: 'en',
        ),
      );

      _ytController.addListener(() {
        print('for $_id got isPlaying state ${_ytController.value.isPlaying}');
        if (cStates[_id] != _ytController.value.isPlaying) {
          if (mounted) {
            setState(() {
              cStates[_id] = _ytController.value.isPlaying;
            });
          }
        }
      });

      lYTC.add(_ytController);
    }
  }

  @override
  void dispose() {
    for (var element in lYTC) {
      element.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Tubeloid'),
        centerTitle: true,
        actions: [
          IconButton(
            onPressed: () {},
            icon: const Icon(Icons.menu_outlined),
          )
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(10.0),
        child: ListView.builder(
          itemCount: _videoUrlList.length,
          shrinkWrap: true,
          itemBuilder: (context, index) {
            YoutubePlayerController _ytController = lYTC[index];
            String _id = YoutubePlayer.convertUrlToId(_videoUrlList[index])!;
            String curState = 'undefined';
            if (cStates[_id] != null) {
              curState = cStates[_id]? 'playing':'paused';
            }
            return Padding(
              padding: const EdgeInsets.symmetric(vertical: 8.0),
              child: Stack(
                alignment: Alignment.bottomCenter,
                children: [
                  Container(
                    height: 220.0,
                    decoration: const BoxDecoration(
                      color: Color(0xfff5f5f5),
                      borderRadius: BorderRadius.all(Radius.circular(12)),
                    ),
                    child: ClipRRect(
                      borderRadius: const BorderRadius.all(Radius.circular(12)),
                      child: YoutubePlayer(
                        controller: _ytController,
                        showVideoProgressIndicator: true,
                        progressIndicatorColor: Colors.lightBlueAccent,
                        bottomActions: [
                          CurrentPosition(),
                          ProgressBar(isExpanded: true),
                          FullScreenButton(),
                        ],
                        onReady: (){
                          print('onReady for $index');
                        },
                        onEnded: (YoutubeMetaData _md) {
                          _ytController.seekTo(const Duration(seconds: 0));
                        },
                      ),
                    ),
                  ),
                  Container(
                    width: double.infinity,
                    decoration: BoxDecoration(
                      color: Colors.white.withOpacity(0.9),
                      borderRadius: const BorderRadius.only(
                        bottomRight: Radius.circular(12),
                        bottomLeft: Radius.circular(12),
                      ),
                    ),
                    child: Text(curState, textScaleFactor: 1.5,),
                  )
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

Answered By – Владимир Прихоженко

Answer Checked By – Pedro (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *