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

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

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

Web | NDA

Casio

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

Web | В работе

Makita

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

Mobile

Airspector

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

Mobile | NDA

Shell

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

Web

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

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

Mobile | NDA

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

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

Mobile

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

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

Mobile | NDA

DHL Express

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

Web

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

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

Web

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

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

Web

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

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

Mobile | NDA

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

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

Mobile

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

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

Web

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

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

Web

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

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

Web | NDA

Газпром

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

Web | Minicase

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

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

Web

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

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

Mobile

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

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

Web | NDA

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

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

Web | В работе

Русплитка

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

Mobile | NDA

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

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

Mobile

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

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

Web

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

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

Mobile

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

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

Mobile

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

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

Web | NDA

Ecco

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

Web | NDA

НЛМК

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

Mobile | NDA

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

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

Web

HR-сайт для SOKOLOV

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

Mobile | NDA

Сбер

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

Mobile

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

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

Web | NDA

Samsung

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

Web | NDA

Nike

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

Web | Minicase

Аникура

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

Mobile | NDA

Rockwool

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

Web

Travelpayouts

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

Web | NDA

Мегафон

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

Mobile

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

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

Mobile

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

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

Web | NDA

М.Видео

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

Web

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

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

Web | NDA

Mercedes-Benz

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

НАПИСАТЬ