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, даже с учётом моих оптимизационных трюков, по всем параметрам.

Web | NDA

Мегафон

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

Web

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

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

Mobile

Airspector

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

Web | В работе

Makita

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

Web

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

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

Web

HR-сайт для SOKOLOV

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

Mobile | NDA

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

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

Web | Minicase

Аникура

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

Mobile | NDA

Rockwool

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

Web

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

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

Web | NDA

М.Видео

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

Web | Minicase

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

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

Mobile

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

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

Mobile

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

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

Mobile | NDA

DHL Express

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

Mobile | NDA

Сбер

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

Mobile

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

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

Mobile | NDA

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

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

Web | NDA

НЛМК

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

Web

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

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

Web | В работе

Русплитка

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

Web | NDA

Ecco

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

Mobile | NDA

Shell

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

Web | NDA

Газпром

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

Mobile

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

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

Web

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

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

Web

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

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

Web

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

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

Mobile | NDA

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

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

Web

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

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

Web

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

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

Web

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

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

Web

Travelpayouts

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

Web | NDA

Mercedes-Benz

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

Web | NDA

Casio

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

Web | NDA

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

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

Web | NDA

Nike

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

Mobile

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

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

Mobile

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

Обложка кейса «Приложение для пекарен Хлеб Хмельницкого»

Mobile

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

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

Web | NDA

Samsung

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

Mobile

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

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

Mobile

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

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

Mobile | NDA

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

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

НАПИСАТЬ