ru:https://highload.today/blogs/kak-sozdat-potok-videodannyh-s-pomoshhyu-node-js-razbirayu-detali-na-svoem-proekte/ ua:https://highload.today/uk/blogs/yak-stvoryty-potik-videodanyh-za-dopomogoyu-node-js/
logo
Back-end      05/08/2022

Як створити потік відеоданих за допомогою Node.js: розбираю деталі на своєму проєкті

Владислав Хирса BLOG

Senior Backend Developer | Node.js | NestJS | Express.js | SQL | NoSQL | AWS

Привіт усім! Мене звати Владислав Хирса, я — Software Engineer у Grid Dynamics. Сьогодні я розповім вам, як за допомогою Node.js створити потік відеоданих. Стаття буде корисною для тих, хто ще тільки розбирається у темі Streams in Node.js (наприкінці буде трохи важливої теорії).

Створити потік даних у Node.js зараз просто, але чи все ми розуміємо про те, яким чином працює ця абстракція?

Знайти код ви можете за посиланням.

Почнемо!

Спочатку запустимо наш сервер, перебуваючи у папці проєкту, командою npm start.

Далі наш сервер запуститься за адресою http://localhost:8000/. Перейшовши за посиланням, у вашому браузері має з’явитися вкладка такого змісту:

Тут ми можемо бачити в дії наш проєкт. І тепер те, задля чого ми тут — дізнатися, як все працює.

Файл index.js

Натисніть, щоб роздивитися

Натисніть, щоб роздивитися

Тут ми створили простий сервер, функцією зворотного виклику призначили функцію router, яка отримує параметри request і response. Далі ми перевіряємо, чи маємо по отриманому request.url збіги в нашому об’єкті runnersByRouts за даним іменем ключа. Якщо так — то викликаємо відповідну функцію, якщо ні — то повертаємо відповідь про помилку до клієнта.

При відкритті вкладки в браузері за нашим посиланням до нашого сервера надходить запит з url /, і ми віддаємо нашу сторінку, файл index.html.

Курс English For IT: Communication від Enlgish4IT.
Почни легко працювати та спілкуватися з мультикультурними командами та міжнародними клієнтами. Отримайте знижку 10% за промокодом ITCENG.
Інформація про курс

Файл src/send-home-page.js

Натисніть, щоб роздивитися

Натисніть, щоб роздивитися

Спочатку ми знаходимо шлях до нашої папки за допомогою url.fileURLToPath(new URL('.', import.meta.url)), назначаємо тип контенту, який збираємось надіслати клієнту res.setHeader('Content-Type', 'text/html'), далі створюємо читабельний потік fs.createReadStream(__dirname + '../public/index.html') і на останньому рядку викликаємо наш потік за допомогою функції pipeline().

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

Файл index.html

Натисніть, щоб роздивитися

Натисніть, щоб роздивитися

Розглянемо декілька важливих для нас атрибутів в html елементі <video>:

  1. src="/video-stream" — при рендерингу нашої сторінки в браузері ми звертаємось до нашого серверу за адресою http://localhost:8000/video-stream і отримуємо наше відео.
  2. controls — цей атрибут дає користувачу можливість мати контроль над відео (старт/пауза, звук тощо).
  3. preload="auto" — у специфікації вказано те, що весь відеофайл може завантажитись навіть якщо користувач не буде використовувати його. Але на практиці все залежить від браузера і відбуватиметься, скоріш за все, більш динамічно. Наприклад, так — ваше відео буде завантажено приблизно на 1 хв. наперед і через кожні 5 секунд пройденого відео дозавантажаться ще 5 секунд і т.д.

Тож нам надходить запит з url /video-stream і ми викликаємо нашу функцію sendVideoFile.

Файл src/send-video-file.js

Натисніть, щоб роздивитися

Натисніть, щоб роздивитися

У нашій функції sendVideoFile все починається з того, що:

  1. Ми створюємо абсолютний шлях до файлу — шлях, який нам вказали в параметрі pathToVideo. Було public/nature.mp4 — стало /your_folder/your_folder/project_folder/public/nature.mp4.
  2. Онлайн курс з промт інжинірингу та ефективної роботи з ШІ від Powercode academy.
    Курс-інтенсив для отримання навичок роботи з ChatGPT та іншими інструментами ШІ для професійних та особистих задач, котрі допоможуть як новачку, так і професіоналу.
    Записатися на курс
  3. fs.statSync(resolvedPath).size — дізнаємося розмір файлу в байтах.
  4. req.headers.range — отримуємо параметр range (bytes=12582912-), тобто те, з якої позиції треба завантажувати відео в байтах.

Залежно від браузера і плеєра параметр range може бути null або, наприклад, bytes=123456-, тож у нас є дві різні функції для обробки цих насправді різних підходів.

Файл utils/create-video-stream.js

Натисніть, щоб роздивитися

Натисніть, щоб роздивитися

Тут вже все просто — код схожий з тим, який ми вже розглядали в src/send-home-page.js. Єдина різниця в тому, що ми назначаємо обов’язкові заголовки Content-Type і Content-Length для того, щоб браузер розумів, якого типу ми надсилаємо йому інформацію і якого розміру. Це необхідно як для коректної роботи плеєра, так і для подальшої взаємодії плеєра з сервером під час наступних транзакцій даних.

І внизу також один із найчастіших випадків — коли параметр range існує.

Файл utils/create-video-stream-by-range.js

Натисніть, щоб роздивитися

Натисніть, щоб роздивитися

Тут у нас є функція getChunkData, яка бере вхідний параметр range та fileSize та здійснює наступні кроки:

  1. Бере пару значень з range = bytes=36634624- і отримує масив parts = [ '36634624', '' ].
  2. Обчислює значення start = 36896768, end = 86890916, chunkSize = 49994149.

Файл utils/get-chunk-data.js

Натисніть, щоб роздивитися

Натисніть, щоб роздивитися

Також в createVideoStreamByRange ми вказуємо обов’язковим статус відповіді 206, а також Content-Range — яку частину даних з усього відео ми відправляємо, а також Accept-Ranges — у якому форматі дані, які ми відправляємо.

Ще кілька доповнень, які корисно знати:

Англійська для початківців від Englishdom.
Для тих, хто тільки починає вивчати англійську і хоче вміти використовувати базову лексику і граматику.
Реєстрація на курс
  1. pipeline — чому краще використовувати pipeline(), а не чергу pipe() при роботі з потоками. У функції pipeline() останнім аргументом є функція зворотного виклику. Ми використовували її у вищезазначених прикладах коду. Якщо виникне помилка в будь-якому з переданих потоків, то ми її можемо обробити в одному місці. Також pipeline() самостійно закриває всі закінчені, але не закриті запити до сервера. Наприклад, коли ми використовуємо someReadStream(path).pipe(res), то після помилки або закінчення передачі даних запит до сервера скоріш за все не зачинить, через що виникають незрозумілі та дуже вагомі помилки та втрата оперативної пам’яті. Про це ви можете почитати детальніше тут.
  2. ES modules — щоб використати імпортування функціоналу за допомогою imports, в js-файлах нам потрібно вказати в package.json тип таким чином { "type": "module" }, але такі змінні, як __dirname та __filename не існують в ES modules, а є можливістю CommonJS. З цим ви можете ознайомитись за посиланням з офіційної документації. Тож знайти шляхи ми можемо таким чином:

const __filename = url.fileURLToPath(import.meta.url);

const __dirname = url.fileURLToPath(new URL('.', import.meta.url));

  1. highWaterMark — це значення розміру внутрішнього буферу, тобто кількість даних у байтах, які ми можемо прочитати за один раз, тобто один chunk даних (за замовчуванням він 64kB). Також значення highWaterMark ми можемо змінити при створенні потоку fs.createReadStream(path, { highWaterMark: 2 }), тепер ми будемо зчитувати наш файл по два символи за раз, а також можемо дізнатися його розмір таким чином: readStream.readableHighWaterMark, значення за умовчанням буде 65536 байтів.
Натисніть, щоб роздивитися

Натисніть, щоб роздивитися

Як працює потік і відправлення даних в деталях?

Cпершу ми створюємо потік зі зчитування файлу і призначаємо його в зміну readStream, після цього використовуємо його в функції pipeline(), далі chunk даних передається до потоку res (тобто response, якщо повністю) і тоді res його отримує і відправляє клієнту за допомогою res.write(chunk). Щоразу коли ми читаємо і передаємо йому наш chunk даних, то в кінці, коли вже немає даних для зчитування, викликається подія end для кожного потоку і функція pipeline() самостійно закриває їх. Що дуже важливо, у випадку з res після останнього викликається res.end() і наш запит до сервера успішно закінчується.

На цьому все, дякую всім за увагу. Продуктивного вам кодування 😉

Якщо ви знайшли помилку, будь ласка, виділіть фрагмент тексту та натисніть Ctrl+Enter.

Онлайн-курс Бізнес-аналіз. Basic Level від Ithillel.
В ході курсу студенти навчаться техніці збору і аналізу вимог, документуванню та управлінню документацією, управлінню ризиками та змінами, а також навчаться моделювати процеси і прототипуванню.
Приєднатися

Цей матеріал – не редакційний, це – особиста думка його автора. Редакція може не поділяти цю думку.

Топ-5 найпопулярніших блогерів березня

PHP Developer в ScrumLaunch
Всего просмотровВсього переглядів
2229
#1
Всего просмотровВсього переглядів
2229
Founder at Shallwe, Python Software Engineer (Django/React)
Всего просмотровВсього переглядів
111
#2
Всего просмотровВсього переглядів
111
Career Consultant в GoIT
Всего просмотровВсього переглядів
93
#3
Всего просмотровВсього переглядів
93
CEO & Founder в Trustee
Всего просмотровВсього переглядів
92
#4
Всего просмотровВсього переглядів
92
Рейтинг блогерів

Найбільш обговорювані статті

Топ текстів

Ваша жалоба отправлена модератору

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: