Amiga

Вернуться к блогу

Интеграция видеоплеера YouTube во Flutter

  • Опубликовано: 03.06.2024
  • Oбновлено: 02.06.2025
  • Время чтения: 10 минут

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 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 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);

                   },

              ),

           ),

        ],

   ), 

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, но с куда меньшей задержкой загрузки видео.

После эти изменения вынес в форк репозитория пакета.

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

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

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

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

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

Mobile | NDA

Сбер

Обложка кейса «Сбер»

Mobile

Программа лояльности Vaillant Group

Обложка кейса «Программа лояльности Vaillant Group»

Web | NDA

Nike

Обложка кейса «Nike»

Web | NDA

Casio

Обложка кейса «Casio»

Web | NDA

Ecco

Обложка кейса «Ecco»

Mobile

Мобильное приложение для АЗС ХТК

Обложка кейса «Мобильное приложение для АЗС ХТК»

Mobile

Приложение с интеграцией ML

Обложка кейса «Приложение  с интеграцией ML»

Web

Образовательный портал Школа гениев

Обложка кейса «Образовательный портал Школа гениев»

Web | NDA

Samsung

Обложка кейса «Samsung»

Mobile

Мобильное приложение для сети аптек «Ваша №1»

Обложка кейса «Мобильное приложение для сети аптек «Ваша №1»»

Web

Маркетплейс специалистов Gigoo

Обложка кейса «Маркетплейс специалистов Gigoo»

Web

Имиджевый сайт «Шахтинская плитка»

Обложка кейса «Имиджевый сайт «Шахтинская плитка»»

Web | Minicase

Интернет-магазин мебели Трио

Обложка кейса «Интернет-магазин мебели Трио»

Web

Сайт для туркластера «Арктический»

Обложка кейса «Сайт для туркластера «Арктический»»

Mobile

Airspector

Обложка кейса «Airspector»

Mobile | NDA

DHL Express

Обложка кейса «DHL Express»

Web | В работе

Makita

Обложка кейса «Makita»

Mobile

Приложение-сканер товаров с TV

Обложка кейса «Приложение-сканер товаров с TV»

Web | NDA

Мегафон

Обложка кейса «Мегафон»

Mobile

Интернет-магазин Bravo

Обложка кейса «Интернет-магазин Bravo»

Mobile

Образовательный проект Easy

Обложка кейса «Образовательный проект Easy»

Web | Minicase

Аникура

Обложка кейса «Аникура»

Web

Крупное федеральное СМИ

Обложка кейса «Крупное федеральное СМИ»

Mobile | NDA

Shell

Обложка кейса «Shell»

Web

HR-сайт для SOKOLOV

Обложка кейса «HR-сайт для SOKOLOV»

Web | NDA

ERP-система лизинга автопарка

Обложка кейса «ERP-система лизинга автопарка»

Mobile | NDA

Интернет-магазин NL Store

Обложка кейса «Интернет-магазин  NL Store»

Web

Корпоративный портал ЕМС Team

Обложка кейса «Корпоративный портал ЕМС Team»

Mobile | NDA

Приложение для здоровья CW Clinic

Обложка кейса «Приложение для здоровья CW Clinic»

Web

Транспортная компания №1

Обложка кейса «Транспортная компания №1»

Web

Маркетплейс горного оборудования

Обложка кейса «Маркетплейс горного оборудования»

Web

Маркетплейс нефтяных продуктов Proleum

Обложка кейса «Маркетплейс нефтяных продуктов Proleum»

Web | В работе

Русплитка

Обложка кейса «Русплитка»

Web

Travelpayouts

Обложка кейса «Travelpayouts»

Web | NDA

Газпром

Обложка кейса «Газпром»

Web | NDA

НЛМК

Обложка кейса «НЛМК»

Mobile | NDA

Rockwool

Обложка кейса «Rockwool»

Mobile | NDA

Бизнес-приложение Жёлтая печать

Обложка кейса «Бизнес-приложение Жёлтая печать»

Web | NDA

Mercedes-Benz

Обложка кейса «Mercedes-Benz»

Mobile

Мобильное приложение CMstore

Обложка кейса «Мобильное приложение CMstore»

Web | NDA

М.Видео

Обложка кейса «М.Видео»

Web

B2B-сервис по отработке обращений «Авеста»

Обложка кейса «B2B-сервис по отработке обращений «Авеста»»

Mobile | NDA

AI-приложение Get Art

Обложка кейса «AI-приложение Get Art»

Mobile

Приложение для пекарен Хлеб Хмельницкого

Обложка кейса «Приложение для пекарен Хлеб Хмельницкого»
Хотите связаться с владельцем
компании напрямую?
Дмитрий Тарасов
Дмитрий Тарасов
СЕО

НАПИСАТЬ