Membangun Aplikasi Streaming Video Dengan Nuxt.js, Node, dan Express
Diterbitkan: 2022-03-10Video berfungsi dengan streaming. Ini berarti bahwa alih-alih mengirim seluruh video sekaligus, video dikirim sebagai kumpulan potongan kecil yang membentuk video penuh. Ini menjelaskan mengapa video buffer saat menonton video di broadband lambat karena hanya memutar potongan yang telah diterima dan mencoba memuat lebih banyak.
Artikel ini ditujukan untuk pengembang yang ingin mempelajari teknologi baru dengan membangun proyek aktual: aplikasi streaming video dengan Node.js sebagai backend dan Nuxt.js sebagai klien.
- Node.js adalah runtime yang digunakan untuk membangun aplikasi yang cepat dan skalabel. Kami akan menggunakannya untuk menangani pengambilan dan streaming video, membuat thumbnail untuk video, dan menyajikan teks dan subtitel untuk video.
- Nuxt.js adalah kerangka kerja Vue.js yang membantu kita membangun aplikasi Vue.js yang dirender server dengan mudah. Kami akan menggunakan API kami untuk video dan aplikasi ini akan memiliki dua tampilan: daftar video yang tersedia dan tampilan pemutar untuk setiap video.
Prasyarat
- Pemahaman tentang HTML, CSS, JavaScript, Node/Express, dan Vue.
- Sebuah editor teks (misalnya VS Code).
- Peramban web (misalnya Chrome, Firefox).
- FFmpeg diinstal pada workstation Anda.
- Node.js. nvm.
- Anda bisa mendapatkan kode sumber di GitHub.
Menyiapkan Aplikasi Kami
Dalam aplikasi ini, kami akan membangun rute untuk membuat permintaan dari frontend:
- rute
videos
untuk mendapatkan daftar video dan datanya. - rute untuk mengambil hanya satu video dari daftar video kami.
- rute
streaming
untuk streaming video. - rute
captions
untuk menambahkan teks ke video yang kami streaming.
Setelah rute kami dibuat, kami akan membuat perancah frontend Nuxt
kami, di mana kami akan membuat Home
dan halaman player
dinamis. Kemudian kami meminta rute videos
kami untuk mengisi halaman beranda dengan data video, permintaan lain untuk mengalirkan video di halaman player
kami, dan akhirnya permintaan untuk menyajikan file teks untuk digunakan oleh video.
Untuk mengatur aplikasi kami, kami membuat direktori proyek kami,
mkdir streaming-app
Menyiapkan Server Kami
Di direktori streaming-app
kami, kami membuat folder bernama backend
.
cd streaming-app mkdir backend
Di folder backend kami, kami menginisialisasi file package.json
untuk menyimpan informasi tentang proyek server kami.
cd backend npm init -y
kita perlu menginstal paket-paket berikut untuk membangun aplikasi kita.
-
nodemon
secara otomatis me-restart server kami ketika kami melakukan perubahan. -
express
memberi kita antarmuka yang bagus untuk menangani rute. -
cors
akan memungkinkan kami untuk membuat permintaan lintas-asal karena klien dan server kami akan berjalan pada port yang berbeda.
Di direktori backend kami, kami membuat assets
folder untuk menyimpan video kami untuk streaming.
mkdir assets
Salin file .mp4
ke folder aset, dan beri nama video1
. Anda dapat menggunakan video sampel pendek .mp4
yang dapat ditemukan di Github Repo.
Buat file app.js
dan tambahkan paket yang diperlukan untuk aplikasi kita.
const express = require('express'); const fs = require('fs'); const cors = require('cors'); const path = require('path'); const app = express(); app.use(cors())
Modul fs
digunakan untuk membaca dan menulis ke dalam file dengan mudah di server kami, sedangkan modul path
menyediakan cara bekerja dengan direktori dan jalur file.
Sekarang kita membuat rute ./video
. Saat diminta, itu akan mengirim file video kembali ke klien.
// add after 'const app = express();' app.get('/video', (req, res) => { res.sendFile('assets/video1.mp4', { root: __dirname }); });
Rute ini menyajikan file video video1.mp4
saat diminta. Kami kemudian mendengarkan server kami di port 3000
.
// add to end of app.js file app.listen(5000, () => { console.log('Listening on port 5000!') });
Sebuah skrip ditambahkan dalam file package.json
untuk memulai server kami menggunakan nodemon.
"scripts": { "start": "nodemon app.js" },
Kemudian di terminal Anda jalankan:
npm run start
Jika Anda melihat pesan Listening on port 3000!
di terminal, maka server bekerja dengan benar. Arahkan ke https://localhost:5000/video di browser Anda dan Anda akan melihat video diputar.
Permintaan Untuk Ditangani Oleh Frontend
Di bawah ini adalah permintaan yang akan kami buat ke backend dari frontend kami yang perlu ditangani oleh server.
-
/videos
Mengembalikan array data mockup video yang akan digunakan untuk mengisi daftar video di halamanHome
di frontend kami. -
/video/:id/data
Mengembalikan metadata untuk satu video. Digunakan oleh halamanPlayer
di frontend kami. -
/video/:id
Streaming video dengan ID yang diberikan. Digunakan oleh halamanPlayer
.
Mari kita buat rute.
Kembalikan Data Mockup Untuk Daftar Video
Untuk aplikasi demo ini, kami akan membuat larik objek yang akan menyimpan metadata dan mengirimkannya ke frontend saat diminta. Dalam aplikasi nyata, Anda mungkin akan membaca data dari database, yang kemudian akan digunakan untuk menghasilkan array seperti ini. Demi kesederhanaan, kita tidak akan melakukannya dalam tutorial ini.
Di folder backend kami, buat file mockdata.js
dan isi dengan metadata untuk daftar video kami.
const allVideos = [ { id: "tom and jerry", poster: 'https://image.tmdb.org/t/p/w500/fev8UFNFFYsD5q7AcYS8LyTzqwl.jpg', duration: '3 mins', name: 'Tom & Jerry' }, { id: "soul", poster: 'https://image.tmdb.org/t/p/w500/kf456ZqeC45XTvo6W9pW5clYKfQ.jpg', duration: '4 mins', name: 'Soul' }, { id: "outside the wire", poster: 'https://image.tmdb.org/t/p/w500/lOSdUkGQmbAl5JQ3QoHqBZUbZhC.jpg', duration: '2 mins', name: 'Outside the wire' }, ]; module.exports = allVideos
Kita bisa melihat dari atas, setiap objek berisi informasi tentang video. Perhatikan atribut poster
yang berisi tautan ke gambar poster video.
Mari kita buat rute videos
karena semua permintaan kita untuk dibuat oleh frontend diawali dengan /videos
.
Untuk melakukan ini, mari buat folder routes
dan tambahkan file Video.js
untuk rute /videos
kita. Dalam file ini, kami akan membutuhkan express
dan menggunakan router ekspres untuk membuat rute kami.
const express = require('express') const router = express.Router()
Saat kita pergi ke rute /videos
, kita ingin mendapatkan daftar video kita, jadi mari kita minta file mockData.js
ke dalam file Video.js
kita dan buat permintaan kita.
const express = require('express') const router = express.Router() const videos = require('../mockData') // get list of videos router.get('/', (req,res)=>{ res.json(videos) }) module.exports = router;
Rute /videos
sekarang dideklarasikan, simpan file dan server akan otomatis restart. Setelah dimulai, navigasikan ke https://localhost:3000/videos dan array kami dikembalikan dalam format JSON.
Kembalikan Data Untuk Satu Video
Kami ingin dapat membuat permintaan untuk video tertentu dalam daftar video kami. Kami dapat mengambil data video tertentu dalam array kami dengan menggunakan id
yang kami berikan. Mari kita membuat permintaan, masih dalam file Video.js
kita.
// make request for a particular video router.get('/:id/data', (req,res)=> { const id = parseInt(req.params.id, 10) res.json(videos[id]) })
Kode di atas mendapatkan id
dari parameter rute dan mengubahnya menjadi bilangan bulat. Kemudian kami mengirim objek yang cocok dengan id
dari array videos
kembali ke klien.
Streaming Video
Di file app.js
kami, kami membuat rute /video
yang menyajikan video ke klien. Kami ingin titik akhir ini mengirim potongan video yang lebih kecil, alih-alih menyajikan seluruh file video berdasarkan permintaan.
Kami ingin dapat secara dinamis menayangkan salah satu dari tiga video yang ada di larik allVideos
, dan streaming video dalam potongan, jadi:
Hapus rute /video
dari app.js
.
Kami membutuhkan tiga video, jadi salin video contoh dari kode sumber tutorial ke direktori assets/
dari proyek server
Anda. Pastikan nama file untuk video sesuai dengan id
dalam larik videos
:
Kembali ke file Video.js
kami, buat rute untuk streaming video.
router.get('/video/:id', (req, res) => { const videoPath = `assets/${req.params.id}.mp4`; const videoStat = fs.statSync(videoPath); const fileSize = videoStat.size; const videoRange = req.headers.range; if (videoRange) { const parts = videoRange.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize-1; const chunksize = (end-start) + 1; const file = fs.createReadStream(videoPath, {start, end}); const head = { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4', }; res.writeHead(206, head); file.pipe(res); } else { const head = { 'Content-Length': fileSize, 'Content-Type': 'video/mp4', }; res.writeHead(200, head); fs.createReadStream(videoPath).pipe(res); } });
Jika kita menavigasi ke https://localhost:5000/videos/video/outside-the-wire di browser kita, kita bisa melihat video streaming.
Cara Kerja Rute Video Streaming
Ada sedikit kode yang tertulis di rute video streaming kami, jadi mari kita lihat baris demi baris.
const videoPath = `assets/${req.params.id}.mp4`; const videoStat = fs.statSync(videoPath); const fileSize = videoStat.size; const videoRange = req.headers.range;
Pertama, dari permintaan kami, kami mendapatkan id
dari rute menggunakan req.params.id
dan menggunakannya untuk menghasilkan videoPath
ke video. Kami kemudian membaca fileSize
menggunakan sistem file fs
yang kami impor. Untuk video, browser pengguna akan mengirimkan parameter range
dalam permintaan. Ini memungkinkan server mengetahui bagian video mana yang akan dikirim kembali ke klien.
Beberapa browser mengirim rentang dalam permintaan awal, tetapi yang lain tidak. Bagi yang tidak, atau jika karena alasan lain browser tidak mengirim rentang, kami menanganinya di blok else
. Kode ini mendapatkan ukuran file dan mengirim beberapa potongan pertama video:
else { const head = { 'Content-Length': fileSize, 'Content-Type': 'video/mp4', }; res.writeHead(200, head); fs.createReadStream(path).pipe(res); }
Kami akan menangani permintaan berikutnya termasuk rentang dalam blok if
.
if (videoRange) { const parts = videoRange.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize-1; const chunksize = (end-start) + 1; const file = fs.createReadStream(videoPath, {start, end}); const head = { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4', }; res.writeHead(206, head); file.pipe(res); }
Kode di atas membuat aliran baca menggunakan nilai start
dan end
rentang. Setel Content-Length
dari header respons ke ukuran potongan yang dihitung dari nilai start
dan end
. Kami juga menggunakan kode HTTP 206, yang menandakan bahwa respons berisi sebagian konten. Ini berarti browser akan terus membuat permintaan hingga mengambil semua potongan video.
Apa yang Terjadi Pada Koneksi Tidak Stabil
Jika pengguna berada pada koneksi yang lambat, aliran jaringan akan memberi sinyal dengan meminta sumber I/O dijeda hingga klien siap untuk lebih banyak data. Ini dikenal sebagai tekanan balik . Kita dapat mengambil contoh ini satu langkah lebih jauh dan melihat betapa mudahnya memperluas aliran. Kami juga dapat dengan mudah menambahkan kompresi!
const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize-1; const chunksize = (end-start) + 1; const file = fs.createReadStream(videoPath, {start, end});
Kita dapat melihat di atas bahwa ReadStream
dibuat dan menyajikan potongan video demi potongan.
const head = { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4', }; res.writeHead(206, head); file.pipe(res);
Header permintaan berisi Content-Range
, yang merupakan perubahan awal dan akhir untuk mendapatkan potongan video berikutnya untuk streaming ke frontend, content-length
adalah potongan video yang dikirim. Kami juga menentukan jenis konten yang kami streaming yaitu mp4
. Penulisan 206 diatur untuk merespons hanya dengan aliran yang baru dibuat.
Membuat File Caption Untuk Video Kami
Seperti inilah tampilan file teks .vtt
.
WEBVTT 00:00:00.200 --> 00:00:01.000 Creating a tutorial can be very 00:00:01.500 --> 00:00:04.300 fun to do.
File teks berisi teks untuk apa yang dikatakan dalam video. Ini juga berisi kode waktu kapan setiap baris teks harus ditampilkan. Kami ingin video kami memiliki teks, dan kami tidak akan membuat file teks kami sendiri untuk tutorial ini, jadi Anda dapat menuju ke folder teks di direktori assets
di repo dan mengunduh teks.
Mari buat rute baru yang akan menangani permintaan teks:
router.get('/video/:id/caption', (req, res) => res.sendFile(`assets/captions/${req.params.id}.vtt`, { root: __dirname }));
Membangun Frontend Kami
Untuk memulai bagian visual dari sistem kami, kami harus membangun perancah frontend kami.
Catatan : Anda memerlukan vue-cli untuk membuat aplikasi kami. Jika Anda belum menginstalnya di komputer, Anda dapat menjalankan npm install -g @vue/cli
untuk menginstalnya.
Instalasi
Di root proyek kita, mari buat folder front-end kita:
mkdir frontend cd frontend
dan di dalamnya, kami menginisialisasi file package.json
kami, salin dan tempel yang berikut di dalamnya:
{ "name": "my-app", "scripts": { "dev": "nuxt", "build": "nuxt build", "generate": "nuxt generate", "start": "nuxt start" } }
lalu instal nuxt
:
npm add nuxt
dan jalankan perintah berikut untuk menjalankan aplikasi Nuxt.js:
npm run dev
Struktur File Nuxt kami
Sekarang kita telah menginstal Nuxt, kita dapat mulai meletakkan frontend kita.
Pertama, kita perlu membuat folder layouts
di root aplikasi kita. Folder ini mendefinisikan tata letak aplikasi, tidak peduli halaman yang kita navigasikan. Hal-hal seperti bilah navigasi dan footer kami dapat ditemukan di sini. Di folder frontend, kami membuat default.vue
untuk tata letak default kami ketika kami memulai aplikasi frontend kami.
mkdir layouts cd layouts touch default.vue
Kemudian folder components
untuk membuat semua komponen kita. Kami hanya membutuhkan dua komponen, NavBar
dan komponen video
. Jadi di folder root frontend kami, kami:
mkdir components cd components touch NavBar.vue touch Video.vue
Akhirnya, folder halaman di mana semua halaman kita seperti home
dan about
dapat dibuat. Dua halaman yang kami butuhkan dalam aplikasi ini, adalah halaman home
yang menampilkan semua video dan informasi video kami dan halaman pemutar dinamis yang mengarahkan ke video yang kami klik.
mkdir pages cd pages touch index.vue mkdir player cd player touch _name.vue
Direktori frontend kami sekarang terlihat seperti ini:
|-frontend |-components |-NavBar.vue |-Video.vue |-layouts |-default.vue |-pages |-index.vue |-player |-_name.vue |-package.json |-yarn.lock
Komponen bilah navigasi
NavBar.vue
kami terlihat seperti ini:
<template> <div class="navbar"> <h1>Streaming App</h1> </div> </template> <style scoped> .navbar { display: flex; background-color: #161616; justify-content: center; align-items: center; } h1{ color:#a33327; } </style>
NavBar
memiliki tag h1
yang menampilkan Aplikasi Streaming , dengan sedikit gaya.
Mari impor NavBar
ke tata letak default.vue
kita.
// default.vue <template> <div> <NavBar /> <nuxt /> </div> </template> <script> import NavBar from "@/components/NavBar.vue" export default { components: { NavBar, } } </script>
Tata letak default.vue
sekarang berisi komponen NavBar
dan <nuxt />
setelah itu menandakan di mana halaman yang kita buat akan ditampilkan.
Di index.vue
kami (yang merupakan beranda kami), mari buat permintaan ke https://localhost:5000/videos
untuk mendapatkan semua video dari server kami. Melewati data sebagai prop ke komponen video.vue
yang akan kita buat nanti. Tapi untuk saat ini, kami sudah mengimpornya.
<template> <div> <Video :videoList="videos"/> </div> </template> <script> import Video from "@/components/Video.vue" export default { components: { Video }, head: { title: "Home" }, data() { return { videos: [] } }, async fetch() { this.videos = await fetch( 'https://localhost:5000/videos' ).then(res => res.json()) } } </script>
Komponen Video
Di bawah ini, pertama-tama kami mendeklarasikan prop kami. Karena data video sekarang tersedia di komponen, menggunakan v-for
Vue kami mengulangi semua data yang diterima dan untuk masing-masing, kami menampilkan informasi. Kita dapat menggunakan direktif v-for
untuk mengulang data dan menampilkannya sebagai daftar. Beberapa gaya dasar juga telah ditambahkan.
<template> <div> <div class="container"> <div v-for="(video, id) in videoList" :key="id" class="vid-con" > <NuxtLink :to="`/player/${video.id}`"> <div : class="vid" ></div> <div class="movie-info"> <div class="details"> <h2>{{video.name}}</h2> <p>{{video.duration}}</p> </div> </div> </NuxtLink> </div> </div> </div> </template> <script> export default { props:['videoList'], } </script> <style scoped> .container { display: flex; justify-content: center; align-items: center; margin-top: 2rem; } .vid-con { display: flex; flex-direction: column; flex-shrink: 0; justify-content: center; width: 50%; max-width: 16rem; margin: auto 2em; } .vid { height: 15rem; width: 100%; background-position: center; background-size: cover; } .movie-info { background: black; color: white; width: 100%; } .details { padding: 16px 20px; } </style>
Kami juga memperhatikan bahwa NuxtLink
memiliki rute dinamis, yaitu rute ke /player/video.id
.
Fungsionalitas yang kami inginkan adalah ketika pengguna mengklik salah satu video, itu mulai streaming. Untuk mencapai ini, kami menggunakan sifat dinamis dari rute _name.vue
.
Di dalamnya, kami membuat pemutar video dan mengatur sumber ke titik akhir kami untuk streaming video, tetapi kami secara dinamis menambahkan video mana yang akan diputar ke titik akhir kami dengan bantuan this.$route.params.name
yang menangkap parameter mana yang diterima tautan .
<template> <div class="player"> <video controls muted autoPlay> <source :src="`https://localhost:5000/videos/video/${vidName}`" type="video/mp4"> </video> </div> </template> <script> export default { data() { return { vidName: '' } }, mounted(){ this.vidName = this.$route.params.name } } </script> <style scoped> .player { display: flex; justify-content: center; align-items: center; margin-top: 2em; } </style>
Ketika kami mengklik salah satu video, kami mendapatkan:
Menambahkan File Teks Kami
Untuk menambahkan file track kita, kita pastikan semua file .vtt
di folder captions memiliki nama yang sama dengan id
kita. Perbarui elemen video kami dengan trek, buat permintaan untuk teks.
<template> <div class="player"> <video controls muted autoPlay crossOrigin="anonymous"> <source :src="`https://localhost:5000/videos/video/${vidName}`" type="video/mp4"> <track label="English" kind="captions" srcLang="en" :src="`https://localhost:5000/videos/video/${vidName}/caption`" default> </video> </div> </template>
Kami telah menambahkan crossOrigin="anonymous"
ke elemen video; jika tidak, permintaan teks akan gagal. Sekarang segarkan dan Anda akan melihat teks telah berhasil ditambahkan.
Yang Perlu Diingat Saat Membangun Video Streaming yang Tangguh.
Saat membangun aplikasi streaming seperti Twitch, Hulu atau Netflix, ada beberapa hal yang dipertimbangkan:
- Pipa pemrosesan data video
Ini bisa menjadi tantangan teknis karena server berperforma tinggi diperlukan untuk menyajikan jutaan video kepada pengguna. Latensi atau waktu henti yang tinggi harus dihindari dengan cara apa pun. - Cache
Mekanisme caching harus digunakan saat membangun jenis aplikasi ini misalnya Cassandra, Amazon S3, AWS SimpleDB. - Geografi pengguna
Mempertimbangkan geografi pengguna Anda harus dipikirkan untuk distribusi.
Kesimpulan
Dalam tutorial ini, kita telah melihat cara membuat server di Node.js yang mengalirkan video, membuat teks untuk video tersebut, dan menyajikan metadata video. Kami juga telah melihat cara menggunakan Nuxt.js di frontend untuk menggunakan titik akhir dan data yang dihasilkan oleh server.
Tidak seperti framework lainnya, membangun aplikasi dengan Nuxt.js dan Express.js cukup mudah dan cepat. Bagian keren tentang Nuxt.js adalah caranya mengelola rute dan membuat Anda menyusun aplikasi dengan lebih baik.
- Anda bisa mendapatkan informasi lebih lanjut tentang Nuxt.js di sini.
- Anda bisa mendapatkan kode sumber di Github.
Sumber daya
- “Menambahkan Teks dan Subtitel ke Video HTML5,” Dokumen Web MDN
- “Memahami Teks dan Subtitel,” Screenfont.ca