Создание потоковой платформы HLS VOD (видео по запросу) с помощью Typescript, AdonisJS и AWS

Пример репозитория здесь:

Некоторое время назад я написал версию этой статьи, используя Ruby on Rails. Хотя я был доволен тем, что получилось, в душе я разработчик node. Поэтому я решил переделать этот проект на своем родном языке.

Я также буду использовать AdonisJS в качестве бэкенд-фреймворка вместо Express. Я обнаружил, что фреймворк AdonisJS обладает многими соглашениями и возможностями, которые мне нравились в Rails, но в рамках экосистемы JS.

Что мы будем создавать:

В конце этого проекта у нас будет простая веб-приложение, способное делать следующее:

  • Загружать предоставленные пользователем файлы .mp4 в ведро S3.
  • Транскодирование этих файлов .mp4 в плейлист HLS для потокового вещания
  • Обслуживание этих HLS видео через Cloudfront CDN

Поскольку мы будем использовать S3 и Cloudfront, вам понадобится учетная запись AWS. Однако, учитывая наш текущий сценарий использования, расходы AWS на эти две услуги должны быть номинальными.

Начало работы:

Начните с выполнения следующей команды для инициализации нового приложения AdonisJs.

npm init adonis-ts-app@latest adonis-vod
Войти в полноэкранный режим Выйдите из полноэкранного режима

Выберите следующие настройки:

CUSTOMIZE PROJECT
❯ Select the project structure · web
❯ Enter the project name · adonis-vod
❯ Setup eslint? (y/N) · false
❯ Configure webpack encore for compiling frontend assets? (y/N) · false
Войти в полноэкранный режим Выйти из полноэкранного режима

После завершения работы инструмента cli ваше новое приложение Adonis будет установлено в директории adonis-vod. Откройте его в выбранной вами IDE.

Далее нам нужно установить Lucid, ORM по умолчанию в Adonis, чтобы мы могли создавать модели и взаимодействовать с нашей базой данных.

Выполните следующие действия, чтобы установить Lucid.

npm i @adonisjs/lucid
Войти в полноэкранный режим Выйдите из полноэкранного режима

Затем, после того как npm завершит установку, настройте Lucid, выполнив следующую команду:

node ace configure @adonisjs/lucid
Войти в полноэкранный режим Выйти из полноэкранного режима

Выберите SQLite в качестве драйвера базы данных, а для ‘Выберите, где отображать инструкции…’ выберите ‘В терминале’.

❯ node ace configure @adonisjs/lucid
❯ Select the database driver you want to use …  Press <SPACE> to select
◉ SQLite
◯ MySQL / MariaDB
◯ PostgreSQL
◯ OracleDB
◯ Microsoft SQL Server

...

❯ Select where to display instructions …  Press <ENTER> to select
  In the browser
❯ In the terminal
Вход в полноэкранный режим Выход из полноэкранного режима

Из вывода cli единственная переменная, о которой мы сейчас будем беспокоиться, это переменная DB_CONNECTION env.

Откройте env.ts и отредактируйте его так, чтобы он выглядел следующим образом:

(Обратите внимание на добавление DB_CONNECTION в нижней части файла)

import Env from '@ioc:Adonis/Core/Env'

export default Env.rules({
    HOST: Env.schema.string({ format: 'host' }),
    PORT: Env.schema.number(),
    APP_KEY: Env.schema.string(),
    APP_NAME: Env.schema.string(),
  DRIVE_DISK: Env.schema.enum(['local'] as const),
    NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
    DB_CONNECTION: Env.schema.string(),
})
Вход в полноэкранный режим Выйти из полноэкранного режима

Если вы не знакомы с AdonisJS, то env.ts — это НЕ наш .env файл, а скорее файл, который проверяет наличие необходимых env vars перед запуском нашего сервера.

Если вы откроете файл .env, вы увидите следующее

DB_CONNECTION=sqlite
Вход в полноэкранный режим Выйти из полноэкранного режима

в строке 8, которая является нашей фактической переменной env для выбора SQLite в качестве соединения с базой данных.

Параметры конфигурации для нашей базы данных SQLite хранятся в файле config/database.ts, но нам не нужно вносить никаких изменений для работы нашего приложения.

Далее мы установим драйвер S3 для AdonisJS’ Drive, это позволит нам загружать и хранить наши видеофайлы в AWS S3. Выполните следующую команду:

npm i @adonisjs/drive-s3
Войти в полноэкранный режим Выйти из полноэкранного режима

После завершения установки выполните команду configure:

node ace configure @adonisjs/drive-s3
Войти в полноэкранный режим Выйти из полноэкранного режима

Снова выберите «В терминале» для отображения инструкций. На этот раз мы хотим внести изменения как в имена переменных env, так и в настройки конфигурации.

Сначала откройте .env и измените только что созданные переменные S3 env на следующие:

AWS_ACCESS_KEY=dummyKey
AWS_SECRET_KEY=dummySecret
S3_BUCKET=dummyBucket
S3_REGION=dummyRegion
Войти в полноэкранный режим Выйти из полноэкранного режима

Обратите внимание, что мы меняем имена переменных с S3_KEY и S3_SECRET на AWS_ACCESS_KEY и AWS_SECRET_KEY, поскольку мы также будем использовать Cloudfront. Это изменение имени технически не является необходимым, но я предпочитаю уточнить, что учетные данные AWS, которые мы будем использовать, предназначены не только для S3.

Вы также можете удалить переменную S3_ENDPOINT, поскольку мы не будем ее использовать.

После обновления файла .env откройте config/drive.ts и прокрутите вниз, пока не увидите закомментированные настройки драйвера S3. Откомментируйте конфигурацию драйвера s3 и обновите настройки следующим образом:

s3: {
  driver: 's3',
  visibility: 'private', // <- This is VERY important
  key: Env.get('AWS_ACCESS_KEY'),
  secret: Env.get('AWS_SECRET_KEY'),
  region: Env.get('S3_REGION'),
  bucket: Env.get('S3_BUCKET'),
},
Войти в полноэкранный режим Выйти из полноэкранного режима

(Если после установки и настройки @adonisjs/drive-s3 вы видите ошибку typescript со свойством driver, вам может потребоваться перезапустить ваш сервер Typescript)

Обратите внимание, что мы обновили имена переменных key и secret env с значений по умолчанию на новые. Мы также изменили параметр visibility с ‘public’ на ‘private’. Если вы не измените эту переменную, вы столкнетесь с ошибками при попытке загрузки на s3.

Нам также нужно обновить настройки диска local в файле drive.ts и изменить их следующим образом

local: {
  driver: 'local',
  visibility: 'private',
  root: Application.tmpPath(''),
  serveFiles: false,
  basePath: '/',
},
Войти в полноэкранный режим Выйти из полноэкранного режима

Поскольку мы уже настроили переменные среды для AWS, нам следует также настроить ведро S3, распределение Cloudfront и пользователя IAM.

Начните с входа в консоль AWS. Оттуда введите ‘IAM’ в верхней строке поиска, затем нажмите на результат, чтобы открыть панель «Identity and Access Management».

На панели выберите «пользователи» на левой навигационной панели, затем нажмите синюю кнопку «Добавить пользователей».

Введите имя пользователя, затем выберите «Ключ доступа — программный доступ» для типа учетных данных. Нажмите «Далее: разрешения», чтобы продолжить. Для разрешений

Для простоты мы будем использовать «Прикрепить существующие политики напрямую» с политиками: AmazonS3FullAccess и CloudFrontFullAccess, но в производственном приложении вы, вероятно, захотите использовать более ограничительную политику, следуя принципу наименьших привилегий.

Просто установите флажок рядом с каждой политикой, чтобы прикрепить ее к нашему новому пользователю.

Нажмите «Далее: Tags» и пропустите эту страницу, нажав «Next:Review» в правом нижнем углу экрана. Если у вашего пользователя есть две политики разрешений, перечисленные выше в разделе «Сводка разрешений», вы можете нажать «Создать пользователя».

На следующем экране будут показаны ваши учетные данные для вновь созданного пользователя. Как указано на странице, это последний раз, когда AWS покажет вам эти учетные данные, поэтому лучше всего загрузить их в формате CSV и надежно сохранить файл для дальнейшего использования.

Когда наш пользователь IAM создан, мы можем взять эти учетные данные из файла CSV и добавить их в наш файл .env. Столбец CSV Access key ID соответствует нашей переменной AWS_ACCESS_KEY, а Secret access keyAWS_SECRET_KEY.

Далее мы создадим ведро S3 для хранения загруженных видео. В той же строке поиска, которую вы использовали для поиска «IAM», начните поиск «S3», чтобы открыть панель «S3».

Нажмите оранжевую кнопку «Создать ведро», чтобы открыть панель создания ведра. Вам нужно будет задать ведру глобально уникальное имя, но кроме этого, нам не нужно будет изменять никаких других настроек, поэтому прокрутите страницу до конца и нажмите кнопку «Создать ведро», чтобы завершить создание нового ведра.


Небольшое замечание о ведрах S3: По умолчанию, когда вы создаете новое ведро, параметр «Блокировать весь публичный доступ» будет иметь значение true. Несмотря на то, что мы хотим, чтобы наши пользователи имели доступ к медиафайлам, хранящимся в нашем ведре S3, мы не хотим, чтобы пользователи получали доступ к файлам непосредственно из S3.

Вместо этого мы будем использовать CloudFront CDN Distribution для обслуживания наших файлов S3. Это дает нам 2 основных преимущества: 1 — наши медиафайлы обслуживаются с того сервера CloudFront, который находится ближе всего к запрашивающему пользователю, что дает нашему приложению более высокую скорость, 2 — обслуживание наших медиафайлов с CloudFront дешевле, чем с S3.


Обновите последние 2 переменные env S3_BUCKET и S3_REGION на правильные параметры для только что созданного S3 bucket. Для S3 bucket вам нужно просто имя, а не ARN.

Наконец, давайте настроим наш дистрибутив CloudFront. Откройте панель CloudFront Dashboard так же, как мы делали это с IAM и S3. Нажмите «Создать дистрибутив».

На приборной панели Create Distribution первым параметром, который нам нужно обновить, является «Origin domain», в нашем случае источником должен быть только что созданный вами S3 bucket.

В выпадающем списке поиска «Choose origin domain» вы должны увидеть только что созданное ведро. Выберите его.

В поле «Имя» должно появиться значение по умолчанию после выбора ведра.

Для «S3 bucket access» выберите «Yes use OAI (bucket can restrict access to only CloudFront)». В разделе «Origin access identitiy» должен быть выбран вариант по умолчанию. Затем в разделе «Bucket policy» выберите «Yes, update the bucket policy».

Вам также нужно будет обновить политику заголовков ответа, чтобы использовать «CORS-with-preflight-and-SecurityHeadersPolicy», чтобы мы не столкнулись с проблемами CORS.

В одной из следующих частей мы настроим наш дистрибутив CloudFront на использование Signed Cookies для ограничения доступа к медиа только для пользователей нашего приложения, но пока этих настроек достаточно для начала работы. Прокрутите страницу в самый низ и нажмите «Создать дистрибутив».

Последний шаг настройки, который нам нужно сделать, это добавить наш каталог «tmp» в массив exclude в нашем файле tsconfig.json. Поскольку наше приложение будет генерировать новые файлы (.ts & .m3u8) в директорию tmp, мы хотим исключить эту директорию из просмотра для автоматического перезапуска сервера при изменении файлов.

Откройте tsconfig.json и обновите массив exclude, чтобы он выглядел следующим образом:

...
  "exclude": [
    "node_modules",
    "build",
    "tmp"
  ],
...
Войти в полноэкранный режим Выйти из полноэкранного режима

Если вы забудете добавить «tmp» в exclude, вы увидите, что ваш сервер разработчиков перезапускается несколько раз, пока работает функция транскодирования, которую мы будем реализовывать позже.

После этих шагов по настройке мы можем приступить к созданию нашего приложения.

Выполните следующие две команды, чтобы создать миграцию базы данных и модель для нашего объекта Video.

node ace make:migration videos
node ace make:model Videos
Войти в полноэкранный режим Выйти из полноэкранного режима

Обратите внимание, что AdonisJS автоматически обработает изменение слова между единственным и множественным числом, так что node ace make:model Videos станет app/Models/Video.ts.

Откройте файл database/migrations/[timestamp]_videos.ts и обновите метод public async up() до следующего вида:

public async up () {
  this.schema.createTable(this.tableName, (table) => {
    table.increments('id')

    table.string('name', 255)
    table.string('original_video', 255)
    table.string('hls_playlist', 255)

    /**
     * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
     */
    table.timestamp('created_at', { useTz: true })
    table.timestamp('updated_at', { useTz: true })
  })
}
Вход в полноэкранный режим Выход из полноэкранного режима

В приведенном выше коде мы добавляем 3 колонки в таблицу базы данных: name — это название нашего видео, original_video — это ссылка на путь к нашему оригинальному видео файлу в s3, и hls_playlist — это ссылка на файл hls плейлиста в s3, который наше приложение будет генерировать и загружать.

Обратите внимание на то, что для имен столбцов базы данных в файлах миграции AdonisJS использует соглашение snake_case.

Далее откройте app/Models/Video.ts и обновите определение класса до следующего:

export default class Video extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  public name: string

  @column()
  public originalVideo: string

  @column()
  public hlsPlaylist: string

  @column.dateTime({ autoCreate: true })
  public createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  public updatedAt: DateTime
}
Вход в полноэкранный режим Выйти из полноэкранного режима

В приведенном выше коде вы заметите, что мы добавляем те же колонки базы данных, что и при миграции, только на этот раз в качестве свойств класса Video и в camelCase вместо snake_case.

С нашей моделью базы данных и настройкой миграции мы почти готовы начать использовать нашу базу данных в нашем приложении. Однако прежде чем мы сможем это сделать, нам нужно «применить» нашу миграцию к нашей базе данных. Если вы работаете с MongoDB или NoSQL, концепция миграций может быть для вас новой, но, к счастью, применить миграции баз данных очень просто: выполните следующую команду:

node ace migration:run
Войти в полноэкранный режим Выйти из полноэкранного режима

Приведенная выше команда «применит» миграцию, создав таблицы и столбцы, указанные в нашем файле миграции, и установив им правильный тип данных в базе данных. Если вы хотите прочитать немного больше о миграциях, вот документация AdonisJS по этой теме.

Теперь, когда миграции базы данных запущены, мы можем написать наш контроллер, чтобы мы могли выполнять CRUD-операции над нашей моделью Video. Выполните следующую команду:

node ace make:controller Videos
Войти в полноэкранный режим Выйти из полноэкранного режима

Это автоматически создаст для вас файл app/Controllers/Http/VideosController.ts. Мы добавим 4 метода в наш контроллер Videos, чтобы мы могли создавать новые видео, загружать & перекодировать их, а затем просматривать наши видео через потоковую передачу HLS. Эти 5 методов будут следующими:

  • index() — список всех видео в нашем приложении
  • create() — отображение страницы загрузки видео
  • store() — сохранить новое видео
  • show() — просмотр одного видео.

Для нашего VideosController нам также потребуется установить еще несколько зависимостей. Выполните следующие действия:

npm install @ffmpeg-installer/ffmpeg @ffprobe-installer/ffprobe hls-transcoder
Войдите в полноэкранный режим Выйдите из полноэкранного режима

(Полная информация: hls-transcoder — это пакет, который я поддерживаю)

Ниже приведена реализация класса VideosController. Единственное, что вам нужно будет обновить, это {url: 'YOUR-CLOUDFRONT-URL-HERE'} в методе show(), строка 29. И, как указано в комментарии, не включайте протокол, т.е. https://. Это можно перенести в переменную env, но для наших целей жесткое кодирование вполне подходит и не представляет угрозы безопасности в данном случае.

В остальном, не стесняйтесь копировать/вставлять код видеоконтроллера ниже, и я кратко объясню, что делает каждый метод.

import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Application from '@ioc:Adonis/Core/Application'
import Drive from '@ioc:Adonis/Core/Drive'
import Logger from '@ioc:Adonis/Core/Logger'

import * as fs from 'fs'
import * as path from 'path';
import ffmpeg from '@ffmpeg-installer/ffmpeg';
const ffprobe = require('@ffprobe-installer/ffprobe')

import Video from 'App/Models/Video'

import Transcoder from 'hls-transcoder'


export default class VideosController {
  public async index({ view }: HttpContextContract) {
    const videos = await Video.all()

    return view.render('videos/index.edge', { videos })
  }

  public async create({ view }: HttpContextContract) {
    return view.render('videos/create.edge')
  }

  public async show({ params, view }: HttpContextContract) {
    const video = await Video.findByOrFail('id', params.id)
    const cloudfront = {url: 'YOUR-CLOUDFRONT-URL-HERE'} // <- Put your cloudfront url here DON'T include https://

    return view.render('videos/show.edge', { video, cloudfront })
  }

  public async store({ request, response }: HttpContextContract) {
    const videoFile = request.file('videoFile')
    const name = request.input('name')

    var video = await Video.create({
      name: name,
    })
    // Since id is generated at the database level, we can't use video.id before video is created
    video.originalVideo = `uploads/${video.id}/original.mp4`

    await videoFile?.moveToDisk(`uploads/${video.id}`, {
      name: `original.mp4`
    }, 's3')

    await this.transcodeVideo(video)

    response.redirect().toPath('/videos')
  }


  private async transcodeVideo(video: Video): Promise<void> {
    const local = Drive.use('local')
    const s3 = Drive.use('s3')

    // Get FileBuffer from S3
    const videoFileBuffer = await s3.get(video.originalVideo)
    // Save S3 file to local tmp dir
    await local.put(`transcode/${video.id}/original.mp4`, videoFileBuffer)
    // Get reference to tmp file
    const tmpVideoPath = Application.tmpPath(`transcode/${video.id}/original.mp4`)

    const transcoder = new Transcoder(
      tmpVideoPath,
      Application.tmpPath(`transcode/${video.id}`),
      {
        ffmpegPath: ffmpeg.path,
        ffprobePath: ffprobe.path
      }
    )

    // Log transcoder progress status
    transcoder.on('progress', (progress) => {
      Logger.info(progress)
    })

    // Run the transcoding
    await transcoder.transcode()

    // After transcoding, upload files to S3
    let files
    try {
      files = fs.readdirSync(Application.tmpPath(`transcode/${video.id}/`))
    } catch (err) {
      Logger.error(err)
    }
    await files.forEach( async (file) => {
      const extname = path.extname(file)
      if(extname === '.ts' || extname === '.m3u8') {
        const fileStream = await local.get(`transcode/${video.id}/${file}`)
        await s3.put(`uploads/${video.id}/${file}`, fileStream)
      }
    })

    // Then, clean up our tmp/ dir
    try {
      await fs.rmSync(Application.tmpPath(`transcode/${video.id}/`), { recursive: true })
    } catch (err) {
      Logger.error(err)
    }

    video.hlsPlaylist = `uploads/${video.id}/index.m3u8`

    await video.save()

    return new Promise((resolve) => {
      resolve();
    })
  }
}

Вход в полноэкранный режим Выход из полноэкранного режима

Добавив этот код, позвольте мне кратко объяснить, что мы делаем.

index() — Метод index запрашивает нашу базу данных для каждого видео, затем рендерит шаблон videos/index.edge с массивом videos, переданным в шаблон в качестве состояния.

create() — Метод create возвращает представление с нашей формой для загрузки и создания новых видео объектов.

show() — Метод show, который принимает id видео в качестве параметра url, запрашивает нашу базу данных, чтобы найти это видео по заданному id, и отображает шаблон videos/show.edge с видео и url нашей дистрибуции CloudFront, переданными в шаблон в качестве состояния.

Эти три метода должны быть знакомы вам, если вы раньше использовали такие фреймворки, как Rails или Laravel. Четвертый метод store() также используется в конвенциях Rails и Laravel, но здесь мы добавляем значительную часть пользовательской функциональности, поэтому давайте рассмотрим его более подробно.

Во-первых, наш метод store() принимает 2 входных данных из запроса, videoFile и name. Далее мы используем класс модели Video для создания нового объекта видео и передаем переменную name. Поскольку мы будем использовать video id в наших PATH для транскодирования и хранения, мы не можем пока инстанцировать новый видео объект со свойствами originalVideo или hlsPlaylist.

После создания нового видео мы можем вызвать

video.originalVideo = `uploads/${video.id}/original.mp4`
Войти в полноэкранный режим Выйти из полноэкранного режима

чтобы установить это свойство (оно не сохранится в базе данных, пока мы не вызовем метод .save()!

Затем мы используем метод .moveToDisk из Drive, чтобы загрузить видео нашего пользователя в S3 для хранения.

В следующей строке мы вызываем приватный метод this.transcodeVideo() и передаем в качестве параметра наше video. Давайте рассмотрим, что делает этот метод.

Сначала transcodeVideo() получает ссылки на диски local и s3. Затем, используя диск s3, мы находим файл, который только что загрузили, используя свойство video.originalVideo, которое мы только что установили, и сохраняем ссылку на буфер файлов.

Затем, используя local Drive, мы сохраняем этот файл в каталоге tmp, чтобы использовать его для транскодирования. Если кажется излишним загружать файл на s3, а затем скачивать его локально, то так оно и есть, только настройка транскодера таким образом значительно упрощает перенос транскодирования в фоновое задание, если мы позже решим реализовать очередь заданий.

Сохранив исходный видеофайл в директории tmp, мы передаем его нашему транскодеру и вызываем метод transcoder.transcode(). Транскодер будет испускать события progress каждый раз, когда ffmpeg будет сообщать нам о ходе транскодирования, поэтому я включил вызовы Logger.info для консольной регистрации статуса по мере его получения. Наш фронтенд очень пустой, поэтому мы не будем получать никаких обновлений прогресса или обратной связи на фронтенде.

Наконец, после завершения работы транскодера мы переходим в выходной каталог транскодера и загружаем новые файлы .ts и .m3u8, необходимые для воспроизведения HLS. После загрузки файлов мы удаляем файлы из каталога /tmp, затем устанавливаем свойство hlsPlaylist нашего объекта видео перед вызовом метода video.save() для записи обновлений свойств в базу данных.

В методе store(), после завершения работы this.transcodeVideo(), мы перенаправляем пользователя в индекс Videos, где должно появиться его новое загруженное видео.

В этих двух методах происходит очень многое, и код можно (и нужно) доработать, прежде чем использовать его в производстве, но для нашего примера он работает просто отлично.

Вы также помните, что в нескольких методах мы упоминали шаблоны. Это ссылки на шаблоны Edge, шаблонизатор AdonisJS. Давайте создадим эти файлы сейчас, создав новую папку resources/views/videos И создадим три новых файла:

  • resources/views/videos/create.edge
  • resources/views/videos/index.edge
  • resources/views/videos/show.edge

(Это не совсем учебник по фронтенду, поэтому я загружаю video.js через cdn, где это необходимо, и отказываюсь от какого-либо стиля или дизайна. В общем, это уродливо, но работает).

create.edge

<h1>Create Video</h1>
<a href="/videos">Back</a>
<hr >
<form
  action="{{ route('VideosController.store') }}"
  method="POST"
  enctype="multipart/form-data"
  >
  <div>
    <p>
      <label for="name">Video Name</label>
    </p>
    <input type="text" name="name" />
  </div>
  <div>
    <p>
      <label for="videoFile">Choose a video file:</label>
    </p>
    <input type="file" name="videoFile" accept="video/mp4" />
  </div>
  <br />
  <div>
    <button type="submit">Create Video</button>
  </div>
</form>
Вход в полноэкранный режим Выйти из полноэкранного режима

index.edge

<h1>Videos Index</h1>
<a href="/videos/create">Create new Video</a>
<hr />

<style>
.card {
  margin-top: 2rem;
  margin-bottom: 2rem;
  padding-top: 1rem;
  padding-bottom: 1rem;
  box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
  transition: 0.3s;
}
.card:hover {
  box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
.container {
  padding: 2px 16px;
}
</style>

@each(video in videos)
  <div class="card">
    <div class="container">
      <p><strong>Video Name:</strong> {{ video.name }}</p>
      <a href="/videos/{{ video.id}}">Watch</a>
    </div>
  </div>
@end
Войти в полноэкранный режим Выйти из полноэкранного режима

показать.край

<head>
<link href="https://vjs.zencdn.net/7.19.2/video-js.css" rel="stylesheet" />
</head>

<h1>Show Video</h1>
<a href="/videos">Back</a>
<hr />
<p><strong>Video Name: </strong> {{ video.name }}</p>
<video-js id=vid1 width=852 height=480 class="vjs-default-skin" controls>
  <source
     src="http://{{ cloudfront.url }}/uploads/{{ video.id }}/index.m3u8"
     type="application/x-mpegURL">
</video-js>
<script src="https://vjs.zencdn.net/7.19.2/video.min.js"></script>
<script>
var player = videojs('vid1', {
  limitRenditionByPlayerDimensions: false,
});

player.play();
</script>
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы почти готовы к тестированию нашего приложения, последним шагом перед этим является настройка наших маршрутов для подключения к нашему контроллеру видео.

Откройте файл start/routes.ts и добавьте следующие маршруты ниже маршрута Hello World.

Route.get('/videos/create', 'VideosController.create')
Route.get('/videos', 'VideosController.index')
Route.get('/videos/:id', 'VideosController.show')
Route.post('/videos', 'VideosController.store')
Вход в полноэкранный режим Выйти из полноэкранного режима

После добавления этих маршрутов запустите сервер Adonis в режиме разработки:

npm run dev
Enter fullscreen mode Выйти из полноэкранного режима

В веб-браузере откройте http://localhost:3333/videos, чтобы найти индексную страницу видео.

Нажмите на «Создать новое видео», чтобы открыть /videos/create. Я загружаю копию Blender’s Big Buck Bunny. Как только вы нажмете кнопку «Создать видео», на фронтенде не будет показана обратная связь. Но если вы посмотрите в терминале, где запущен ваш сервер adonis, вы должны начать видеть прогресс ffmpeg через пару секунд.

(Я бы также рекомендовал не использовать огромный видеофайл только из-за отсутствия обратной связи).

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

Если вы нажмете на ссылку «Смотреть», вы попадете на страницу «Показать видео», где (скрестим пальцы) ваше видео будет доступно для воспроизведения в формате HLS в плеере video.js.

Это работает! Чтобы проверить, что наше видео передается нам через HLS, вы можете открыть инструменты разработчика в выбранном вами браузере (я использую Chrome) и посмотреть на сетевые запросы.

Если мы посмотрим на вкладку «Сеть», то увидим различные файлы .ts, загружаемые для нашего видео. (Оно загружается только в 720p, но это проблема frontend video.js, поэтому мы не будем устранять ее здесь).

Если мы хотим протестировать адаптивное воспроизведение HLS, мы можем использовать функцию Network throttling в Chrome для имитации низкой пропускной способности соединения. Я использую предустановку «Slow 3G».

Вы можете видеть, что при медленном соединении наш поток HLS адаптируется и начинает использовать версии с более низким качеством (тем самым уменьшая размер файла), чтобы обеспечить пользователю лучшее качество воспроизведения.

Теперь у нас есть примитивный, но функциональный транскодер HLS и платформа для потоковой передачи VOD. В следующей части мы можем реализовать CloudFront Pre-signed Cookies для ограничения доступа к нашим видео, а также реализовать Job Queue, чтобы дать конечному пользователю лучший опыт во время транскодирования.

Оцените статью
Procodings.ru
Добавить комментарий