Hola, Amigos! Меня зовут Вова Зевеке, я Flutter-разработчик в Amiga. В одном из проектов передо мной стояла задача — интегрировать видеоплеер во Flutter- приложение, с которого можно было бы смотреть видео с YouTube. Казалось бы, подключаем  пакет youtube_player_flutter и всё готово. Но не тут-то было, я столкнулся с рядом проблем, о решении которых рассказываю в статье.

Немного о самом проекте

NL Store — мобильное приложение с функционалом интернет-магазина и личным кабинетом для покупателя. NL международная торговая марка, известна своими протеиновыми коктейлями и снеками, но ассортимент выходит далеко за продукты питания и представляет собой косметические средства, одежду, средства для мытья и уборки.

Мобильное приложение для компании мы разрабатывали с нуля на Flutter. Функционал приложения: каталог, карточка товара, профиль, личный кабинет.

Интернет-магазин доступен всей России и СНГ, а также поддерживается оплата в разной валюте. 

Моей задачей на проекте было интегрировать видеоплеер, чтобы подтягивались видео с Youtube и корректно воспроизводились.

Проблемы при реализации

Долгая загрузка видео 

Вначале я подключил пакет youtube_player_flutter, настроил. «Задача решена». Но спустя некоторое время возникла проблема — буферизация видео занимала очень много времени. 

Я переключил у контроллера видеоплеера флаг «autoPlay» в состояние «true». Видео перестало буферизоваться, время ожидания загрузки видео срезалось на порядок. Однако возникла новая проблема: со включенным флагом «autoPlay» пользователь не может взаимодействовать с видео. Нельзя перемотать, поставить на паузу. Разрешалось только переключать полноэкранный режим.

Казалось, тупик — ограничения пакета не позволяли решить задачу. Стал копаться в пакете. Через некоторое время обнаружил, что у контроллера при включенном «autoPlay» остаётся возможность воздействовать на поле controller.value. Сделав отдельный метод, который ставил контроллеру настройки «playerState: PlayerState.paused, isPlaying: false», мне удалось добиться от видеоплеера возможности ставить паузу даже с включенным «autoPlay». Однако, редактировать кнопку плеера «играть/пауза» я не мог. Пришлось делать свою.

 if (isShowInterface && !isLoading)
              Center(
                child: InkWell(
                  splashColor: Colors.transparent,
                  highlightColor: Colors.transparent,
                  onTap: playPauseVideo,
                  child: Icon(
                    isPaused ? Icons.play_arrow : Icons.pause,
                    color: const Color(0xFFFFFFFF),
                    size: 40,
                  ),
                ),
              ),
  Future<void> playPauseVideo() async{
    setState(() {
      isPaused = !isPaused;
    });
    if (isPaused) {
      pausedMoment = controllerVideo.value.position;
      controllerVideo.value = controllerVideo.value.copyWith(
        playerState: PlayerState.paused,
        isPlaying: false,
      );
    } else {
      controllerVideo.value = controllerVideo.value.copyWith(
        playerState: PlayerState.playing,
        isPlaying: true,
      );
      changeVideoPosition(seconds: pausedMoment.inSeconds);
    }
  }

Перемотка видео

Решил воспользоваться тем, что у контроллера можно задавать флаг «startAt», определяющий, с какого момента видео начинает проигрываться. На основе этого сделал метод, который при попытке перематывания видео, пересоздает плеер с новой точкой старта видео.

  Future<void> changeVideoPosition({required int seconds}) async {
    controllerVideo.removeListener(update);
    setState(() {
      isUpdate = true;
      isLoading = true;
    });
    await Future.delayed(const Duration(milliseconds: 10));
    controllerVideo = YoutubePlayerController(
      initialVideoId: widget.videoId.toString(),
      flags: YoutubePlayerFlags(
        autoPlay: true,
        hideControls: true,
        showLiveFullscreenButton: false,
        startAt: seconds,
      ),
    );
    setState(() {
      isUpdate = false;
    });
    oldMoment = const Duration();
    controllerVideo.addListener(update);
  }

Я использовал ProgressBar, предлагаемый пакетом. И тут тоже был свой подвох с включенным флагом «autoPlay». Он тоже не желал работать, поскольку методы контроллера, которые он вызывал, были заморожены. Пришлось у этого класса дополнить метод _dragEndActions(), дописав вызов метода changeVideoPosition(), функционал которого описан в виджете самого плеера.

  void _dragEndActions() {
    _controller.updateValue(
      _controller.value.copyWith(isControlsVisible: false, isDragging: false),
    );
    _controller.seekTo(_position, allowSeekAhead: true);
    setState(() {
      _touchDown = false;
    });
    _controller.play();
    if (widget.changeVideoPosition != null) {
      widget.changeVideoPosition!();
    }
  }
                      ProgressBar(
                        isExpanded: true,
                        colors: const ProgressBarColors(),
                        controller: controllerVideo,
                        changeVideoPosition: () {
                          changeVideoPosition(seconds: controllerVideo.value.position.inSeconds);
                        },
                      ),

Обратил также внимание на то, что мобильное приложение YouTube пролистывало видео немного вперёд/назад, когда пользователь нажимал два раза подряд на правую/левую половину экрана во время просмотра видео. У этого плеера такой фишки не было, поэтому я её дописал.

  if (!isLoading)
              Row(
                children: [
                  Expanded(
                    child: InkWell(
                      splashColor: Colors.transparent,
                      highlightColor: Colors.transparent,
                      onTap: showInterface,
                      onDoubleTap: () {
                        changeVideoPosition(seconds: controllerVideo.value.position.inSeconds - 5);
                      },
                      child: const SizedBox(height: double.infinity),
                    ),
                  ),
                  Expanded(
                    child: InkWell(
                      splashColor: Colors.transparent,
                      highlightColor: Colors.transparent,
                      onTap: showInterface,
                      onDoubleTap: () {
                        changeVideoPosition(seconds: controllerVideo.value.position.inSeconds + 5);
                      },
                      child: const SizedBox(height: double.infinity),
                    ),
                  ),
                ],
              ),

Таким образом, я постепенно весь интерфейс видеоплеера заменил своим, спрятав старый.

Итог: получился всё тот же плеер, что и дефолтный YoutubePlayer, но с куда меньшей задержкой загрузки видео.

После эти изменения вынес в форк репозитория пакета: https://github.com/Necro73/youtube_player_flutter 

Аудио быстрее видео

Но, как оказалось, счастье длилось недолго – спустя время посыпались баги. Их было много, но, тем не менее, работая через контроллер, я их исправлял, один за другим.

Однако, один баг мне так и не дался – у плеера появилась вредная привычка проигрывать аудиодорожку на пару секунд раньше, чем видео. Пытаясь решить его, упёрся в возможности пакета.

Пришлось искать альтернативный пакет, хотя свой эксперимент бросать было грустно. Покопавшись на просторах интернета, нашёл пакет pod_player. Очень смущал номер версии – 0.2.1. Однако, решил испытать. Сказать, что я был доволен результатом – это ничего не сказать.

Виджет плеера от этого пакета был крайне простой в настройке при этом работал корректно, без багов, видео грузил быстро. Поддерживал как id видео на ютубе, так и url видео в сети, а кроме того, видео в ассетах. В общем, превосходил youtube_player_flutter, даже с учётом моих оптимизационных трюков, по всем параметрам.

Спасибо за внимание! Буду рад вашим комментариям, делитесь, был ли у вас подобный опыт? Как решили проблему?

Комментарии (1)


  1. TranE91
    18.01.2024 18:16

    Been there done that

    Проходил подобное несколько месяцев назад для своего пета. В итоге пришлось родить промежуточный пет-проджект.

    Если копнуть чуть глубже и посмотреть что там и на чем работает то... гуглы закрыли доступы к нативным вставкам youtube player, и поставляют его только через iframe. Те если хочешь нативное решение - пили сам на webview, хватай все лисенеры и обрабатывай сам. Плюс влетал на палки в колеса придуманные для одной платформы, но не обдуманные для других - например флаг autoplay перестал работать без мьюта по дефолту. Было сделано для Web, чтобы перейдя на вкладку у тебя не начало проигрываться видео с выкрученным звуком на всю катушку... но обойти это для мобилок - низя, живи страдай и придумывай обходные пути.

    Не рокет сайнс, больше упирался в отсутствие кроссплатформенных решений. Возможно есть резон попробовать самому с 0 напилить webview вставку?

    Оставлю это здесь(это не Flutter, KMM solution based on Jetpack Compose)
    https://github.com/IlyaPavlovskii/YouTubePlayer