Мистецтво ефектних мініатюр для відео: вчимося у Netflix

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Привіт! Мене звати Сергій Чербаджи, і я розробник із семирічним стажем. За цей час я набув цінного досвіду, працюючи більше п’яти років у компанії ProHabits, де я зараз обіймаю позицію технічного тімліда. Моє захоплення — це розбирання та аналіз різноманітних технічних рішень, які використовуються великими компаніями. Саме ця цікавість спонукала мене поділитися з вами унікальним технічним рішенням, яке впроваджено в компанії Netflix.

У світі, де швидкість і ефективність є ключовими для успішної роботи вебсервісів, Netflix відіграє провідну роль, впроваджуючи передові технології для покращення користувацького досвіду. Моя сьогоднішня стаття присвячена аналізу одного з таких технічних рішень — механізму завантаження мініатюр на вебсайті Netflix. Занурмося разом у світ веброзробки та відкриймо, що стоїть за цим підходом.

Під час аналізу трафіку на вебсайті Netflix через вкладку Network в інструментах розробника браузера я виявив незвичайний механізм завантаження мініатюр. Це спостереження стало відправною точкою для більш глибокого вивчення специфіки та технічних деталей процесу. Тож я вирішив розглянути цей метод, з’ясувати його переваги та принципи роботи в контексті покращення користувацького досвіду під час перегляду відео.

Аналіз швидкості мініатюр

Ефективність мініатюр для передперегляду визначається їхньою здатністю швидко відображатися, мінімізуючи затримку для користувача. Це особливо важливо під час вибору конкретного моменту в епізоді для перемотування. Netflix чудово справляється із цим завданням: як свідчать дані, час відображення мініатюри становить всього 1-2 мс, що є приголомшливим показником швидкості. Саме ця висока швидкість завантаження стала предметом мого інтересу. У наступних розділах я детально поясню техніку, завдяки якій Netflix досягає такої продуктивності.

Технічні деталі: процес створення мініатюр

Процес вибору пригодних кадрів для мініатюр передперегляду містить поділ всього епізоду на рівні інтервали. У випадку Netflix ці інтервали зазвичай налаштовані так, що нова мініатюра генерується кожні 10 секунд. Такий підхід гарантує рівномірне покриття всього контенту епізоду та забезпечує користувачам зручний спосіб навігації, дозволяючи їм швидко та ефективно знаходити цікаві моменти відео.

У цьому розділі я зосереджусь на процесі реалізації мініатюр, а не на стандартних параметрах, як-от розміри та формати.

Як виявилося, Netflix використовує метод URL.createObjectURL для створення мініатюр. Цей статичний метод JavaScript генерує DOMString, який містить URL, що вказує на об’єкт, переданий як параметр. Особливістю цього URL є його тимчасовий характер: він існує лише в контексті документа, в рамках якого був створений. Цей метод дозволяє Netflix представляти мініатюри як об’єкти типу File або Blob, покращуючи так швидкість та ефективність їхнього завантаження.

Практичне застосування: власний експеримент

Тепер, коли ми ознайомилися з теорією, перейдемо до практичних кроків. Для наочності нам знадобиться реальний приклад відео, до якого ми застосуємо процес створення мініатюр. Я обрав одне зі своїх відео, зняте за допомогою дрона. Це відео було поділене на 17 частин, для кожної з яких я створив окремі мініатюри

Структура проєкту: організація файлів

Структура файлів виглядає так:

  • index.html — основний HTML-файл проєкту;
  • index.js — простий сервер на базі Express;
  • assets — папка, що містить підготовлені мініатюри для відображення.

Код проєкту доступний на GitHub за цим посиланням лінк. Однак зосередимося на ключових елементах.

Assets містить відсортовані за порядком мініатюри, наприклад:

  • sepadere_canyon_1.png;
  • sepadere_canyon_2.png;
  • ... і так до 17.

Index.js: сервер виконує такі функції

  • Статичне надання файлів з каталогу assets:
    app.use('/assets', express.static(path.join(__dirname, 'assets')));
  • Отримання списку файлів у каталозі assets:
    app.get('/list-assets', (req, res) => {
      fs.readdir(path.join(__dirname, 'assets'), (err, files) => {
        if (err) {
          return res.status(500).json({ error: 'Failed to read directory' });
        }
        res.json(files);
      });
    });

index.html містить дві кнопки:

Перша кнопка завантажує список зображень та відображає їх на сторінці:

<button onClick="onLoadList();">Load List</button>
<script>
    function onLoadList() {
      fetch('http://localhost:3000/list-assets')
        .then(response => response.json())
        .then(files => {
          const fileList = document.getElementById('fileList');
          files.forEach(file => {
            images.push(file);
            const li = document.createElement('li');
            li.textContent = file;
            fileList.appendChild(li);
          });
        })
        .catch(error => {
          console.error('Failed to fetch list of assets:', error);
        });
</script>

Друга кнопка виконує функцію, яка нас цікавить:

<button onClick="onLoadImages();">Load Images</button>
<script>
     // Запитуємо всі зображення паралельно
    function onLoadImages() {
      images.forEach(link => {
        fetchImage(link);
      });
    }
   //  Для простоти ми будемо зберігати URL-адресу об'єкта BLOB у цій змінній
   let blobURLCache = {};
   // Ось вона основна функція наша
    function fetchImage(imgUrl) {
      fetch(`localhost:3000/assets/${imgUrl}`)
        .then(response => response.blob())
        .then(blob => {
          const objUrl = URL.createObjectURL(blob);
          blobURLCache[imgUrl] = objUrl; // Кешуємо URL-адресу об'єкта BLOB
          splitIntoChunks(Object.keys(blobURLCache).length);
        })
        .catch(error => {
          console.error('Error fetching the image:', error);
        });
    }

Функція onLoadImages() запитує зображення паралельно і кешує URL-адреси об’єктів BLOB. Потім функція splitIntoChunks() відображає блоки на сторінці, імітуючи смугу прокрутки епізоду, і показує мініатюри під час наведення курсора.

function:

splitIntoChunks(numChunks) {
      const lineDiv = document.getElementById('line');
      // Якщо lineDiv не існує, виходимо з функції
      if (!lineDiv) return;
      // Очищаємо вихідний вміст div
      lineDiv.innerHTML = '';
      // Додаємо шматки в lineDiv
      for (let i = 1; i <= numChunks; i++) {
        const chunkDiv = document.createElement('div');
        chunkDiv.classList.add('chunk');
        chunkDiv.innerHTML = `${i}`;
        chunkDiv.addEventListener('mouseover', () => {
          const blob = blobURLCache[`sepadere_canyon_${i}.png`];
          thumbnail.style.display = 'block';
          thumbnail.style.left = event.clientX - 160 + 'px';
          thumbnail.style.top = event.clientY - 80 + 'px';
          thumbnail.src = blob;
        });
        chunkDiv.addEventListener('mousemove', () => {
          // Оновлюємо позицію мініатюри при переміщенні курсора
          thumbnail.style.left = event.clientX - 100 + 'px';
          thumbnail.style.top = event.clientY - 120 + 'px';
        });
        chunkDiv.addEventListener('mouseout', () => {
          thumbnail.style.display = 'none';
        });
        lineDiv.appendChild(chunkDiv);
      }
    }
</script>

Реалізація та інтерфейс

Після запуску сервера на екрані з’являться дві кнопки, описані раніше.

Load List: Під час натискання на цю кнопку ви побачите список файлів. У моєму випадку він виглядає так:

Тут можна вставити опис або скриншот списку файлів, щоб дати читачам уявлення про те, чого чекати.

Load Images: Ця кнопка активує завантаження зображень, як було описано у попередньому розділі.

Під час взаємодії з цим інтерфейсом ви можете спостерігати, як працює механізм завантаження та відображення мініатюр, реалізований у проєкті.

Після натискання на кнопку Load Images, з’являються інтерактивні блоки, які реагують на наведення курсора. Важливо відзначити, що перше завантаження зображень займає в середньому 20 мс, що обумовлено локальним запуском сервера на вашому комп’ютері. Якби сервер був віддаленим, час завантаження збільшився б у десятки разів і залежав би від багатьох факторів, зокрема від пропускної здатність мережі, продуктивності сервера та відстані до сервера.


Тепер, коли ви наведете курсор на будь-який з блоків, що зʼявився, мініатюри відобразяться дуже швидко. Це підкреслює ефективність та швидкість механізму завантаження мініатюр, який аналогічний використаному на сайті Netflix.

Процес не лише візуально привабливий, але також демонструє технічне оснащення системи, здатної швидко та ефективно обробляти та відображати медіаконтент. Це ключовий аспект для забезпечення гладкого та приємного користувацького взаємодії з медіаресурсами.

Висновки: важливість інновацій

Я дослідив процес створення швидких та ефективних мініатюр для відео, надихаючись підходом Netflix. Цей досвід підкреслює важливість технологічних інновацій у поліпшенні користувацького досвіду. Закликаю вас застосовувати ці знання у своїх проєктах, експериментувати та інновувати, щоб розширити межі того, що можливо у веброзробці та дизайні. Ваші ідеї та рішення можуть значно вплинути на майбутнє інтерактивних медіа.

Додатки: ресурси та посилання

Відео | Код
👍ПодобаєтьсяСподобалось5
До обраногоВ обраному1
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

youtu.be/...​sjyQg?si=_HRbx9gqmvUuVStW досить хороше відео на те як влаштований сайт нетфлікса під капотом

Хтось забув перечитати статтю і замінити в 3 абзаці розділу «Реалізація та інтерфейс» плейсхолдерний текст на скріншоти)

Підписатись на коментарі