Membangun Aplikasi Streaming Video Dengan Nuxt.js, Node, dan Express

Diterbitkan: 2022-03-10
Ringkasan cepat Pada artikel ini, kita akan membuat aplikasi streaming video menggunakan Nuxt.js dan Node.js. Secara khusus, kami akan membuat aplikasi Node.js sisi server yang akan menangani pengambilan dan streaming video, membuat gambar mini untuk video Anda, dan menyajikan teks dan subtitel.

Video 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
Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

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 halaman Home di frontend kami.
  • /video/:id/data
    Mengembalikan metadata untuk satu video. Digunakan oleh halaman Player di frontend kami.
  • /video/:id
    Streaming video dengan ID yang diberikan. Digunakan oleh halaman Player .

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:

Hasil akhir aplikasi streaming video nuxt
Streaming video dimulai saat pengguna mengklik thumbnail. (Pratinjau besar)

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