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

Video berfungsi dengan aliran. Ini berarti bahwa alih-alih mengirim seluruh video sekaligus, video dikirim sebagai sekumpulan potongan kecil yang membentuk video lengkap. Ini menjelaskan mengapa video buffer saat menonton video pada broadband lambat karena hanya memutar potongan yang telah diterima dan mencoba memuat lebih banyak.

Artikel ini untuk pengembang yang ingin mempelajari teknologi baru dengan membangun proyek yang sebenarnya: 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 dapat diskalakan. Kami akan menggunakannya untuk menangani pengambilan dan streaming video, membuat thumbnail untuk video, dan menyajikan teks dan subtitle 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

Menyiapkan Aplikasi Kami

Dalam aplikasi ini, kami akan membangun rute untuk membuat permintaan dari frontend:

  • videos rute untuk mendapatkan daftar video dan datanya.
  • rute untuk mengambil hanya satu video dari daftar video kami.
  • streaming untuk mengalirkan video.
  • captions untuk menambahkan teks ke video yang kami streaming.

Setelah rute kita dibuat, kita akan membuat perancah Nuxt kita, di mana kita akan membuat halaman Home dan player dinamis. Kemudian kami meminta videos kami untuk mengisi halaman beranda dengan data video, permintaan lain untuk melakukan streaming video di player kami, dan terakhir permintaan untuk menyajikan file teks untuk digunakan oleh video.

Untuk menyiapkan aplikasi kami, kami membuat direktori proyek kami,

 mkdir streaming-app

Menyiapkan Server Kami

Di streaming-app kami, kami membuat folder bernama backend .

 cd streaming-app mkdir backend

Di folder backend kami, kami menginisialisasi package.json untuk menyimpan informasi tentang proyek server kami.

 cd backend npm init -y

kita perlu menginstal paket berikut untuk membangun aplikasi kita.

  • nodemon restart server kami ketika kami membuat perubahan.
  • express memberi kita antarmuka yang bagus untuk menangani rute.
  • cors akan memungkinkan kita membuat permintaan lintas sumber karena klien dan server kita akan berjalan di port yang berbeda.

Di direktori backend kami, kami membuat assets folder untuk menampung video kami untuk streaming.

 mkdir assets

Salin .mp4 ke dalam folder aset, dan video1 nama video1. Anda dapat menggunakan .mp4 yang dapat ditemukan di Github Repo .

Buat 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())

fs digunakan untuk membaca dan menulis ke file dengan mudah di server kami, sedangkan path menyediakan cara untuk 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 video1.mp4 jika 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 di 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 http: // localhost: 5000 / video di browser Anda dan Anda akan melihat video diputar.

Permintaan Untuk Ditangani Oleh Bagian Depan

Di bawah ini adalah permintaan yang akan kita buat ke backend dari frontend kita yang perlu ditangani oleh server.

  • /videos
    Menampilkan larik data mockup video yang akan digunakan untuk mengisi daftar video di Home di frontend kami.
  • /video/:id/data
    Menampilkan metadata untuk satu video. Digunakan oleh Player di frontend kami.
  • /video/:id
    Streaming video dengan ID tertentu. Digunakan oleh halaman Player

Mari buat rute.

Kembalikan Data Mockup Untuk Daftar Video

Untuk aplikasi demo ini, kita akan membuat larik objek yang akan menyimpan metadata dan mengirimkannya ke frontend saat diminta. Dalam aplikasi nyata, Anda mungkin membaca data dari database, yang kemudian akan digunakan untuk menghasilkan array seperti ini. Demi kesederhanaan, kami tidak akan melakukannya di 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 lihat dari atas, setiap objek berisi informasi tentang video. Perhatikan poster yang berisi link ke gambar poster video.

Mari kita buat videos karena semua permintaan kita untuk dibuat oleh frontend diawali dengan /videos .

Untuk melakukan ini, mari buat routes dan tambahkan file Video.js untuk rute /videos Dalam file ini, kita akan membutuhkan express dan menggunakan router ekspres untuk membuat rute kita.

 const express = require('express') const router = express.Router()

Saat kita pergi ke rute /videos , kita ingin mendapatkan daftar video kita, jadi mari kita membutuhkan mockData.js ke dalam Video.js kita dan membuat 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 itu akan secara otomatis merestart server. Setelah dimulai, navigasikan ke http: // localhost: 3000 / videos dan array kami dikembalikan dalam format JSON.

Kembalikan Data Untuk Video Tunggal

Kami ingin dapat membuat permintaan untuk video tertentu dalam daftar video kami. Kita dapat mengambil data video tertentu dalam array kita dengan menggunakan id kita berikan. Mari buat permintaan, masih di file Video.js

 // 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 integer. Kemudian kami mengirim objek yang cocok dengan id dari videos kembali ke klien.

Streaming Video

Di app.js kami, kami membuat /video yang menyajikan video ke klien. Kami ingin titik akhir ini mengirimkan potongan video yang lebih kecil, alih-alih menyajikan seluruh file video berdasarkan permintaan.

Kami ingin dapat menayangkan secara dinamis salah satu dari tiga video yang ada di allVideos , dan mengalirkan video dalam potongan, jadi:

Hapus rute /video dari app.js

Kami membutuhkan tiga video, jadi salin video contoh dari kode sumber tutorial ke dalam direktori assets/ proyek server Pastikan nama file untuk video tersebut sesuai dengan id dalam larik videos

Kembali ke 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 http: // localhost: 5000 / videos / video / outside-the-wire di browser kita, kita dapat melihat streaming video.

Bagaimana Rute Video Streaming Bekerja

Ada sedikit kode yang ditulis dalam 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 kami impor. Untuk video, browser pengguna akan mengirimkan range dalam permintaan. Ini memungkinkan server mengetahui potongan video mana yang akan dikirim kembali ke klien.

Beberapa browser mengirimkan rentang dalam permintaan awal, tetapi yang lainnya tidak. Bagi yang tidak, atau jika karena alasan lain browser tidak mengirim rentang, kami menanganinya di blok else Kode ini mendapatkan ukuran file dan mengirimkan beberapa bagian pertama dari 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 di 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 header respons ke ukuran potongan yang dihitung dari nilai start dan end . Kami juga menggunakan kode HTTP 206 , yang menandakan bahwa respons berisi konten parsial. Ini berarti browser akan terus membuat permintaan hingga mengambil semua bagian video.

Apa Yang Terjadi Pada Koneksi Tidak Stabil

Jika pengguna berada pada koneksi yang lambat, aliran jaringan akan memberi sinyal dengan meminta agar sumber I / O dijeda hingga klien siap untuk lebih banyak data. Ini dikenal sebagai tekanan balik . Kita dapat mengambil contoh ini selangkah lebih maju dan melihat betapa mudahnya memperluas streaming. 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 video potongan 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 awal dan akhir yang berubah untuk mendapatkan potongan video berikutnya untuk di-streaming ke frontend, content-length adalah potongan video yang dikirim. Kami juga menentukan jenis konten yang kami streaming yaitu mp4 . Kepala tulis 206 disetel untuk merespons hanya dengan aliran yang baru dibuat.

Membuat File Teks 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 diucapkan dalam video. Ini juga berisi kode waktu untuk 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 tersebut.

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 sistem kami, kami harus membangun perancah frontend kami.

Catatan : Anda membutuhkan vue-cli untuk membuat aplikasi kita. 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 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 setelah kami menginstal Nuxt, kami dapat mulai mengatur tampilan frontend kami.

Pertama, kita perlu membuat layouts di root aplikasi kita. Folder ini mendefinisikan tata letak aplikasi, apa pun halaman yang kami buka. Hal-hal seperti bilah navigasi dan footer kami 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 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 tempat semua halaman kami seperti home dan about dapat dibuat. Dua halaman yang kami butuhkan dalam aplikasi ini, adalah home 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 Navbar

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 h1 yang menampilkan Aplikasi Streaming , dengan sedikit gaya.

Mari impor NavBar ke layout default.vue

 // 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 <nuxt /> setelah itu menandakan di mana halaman apa pun yang kami buat akan ditampilkan.

Di index.vue kami (yang merupakan beranda kami), mari buat permintaan ke http://localhost:5000/videos untuk mendapatkan semua video dari server kami. Meneruskan data sebagai prop ke video.vue kita akan membuat 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( 'http://localhost:5000/videos' ).then(res => res.json()) } } </script>

Komponen Video

Di bawah, pertama-tama kami mendeklarasikan prop kami. Karena data video sekarang tersedia di komponen, menggunakan Vue's v-for kami mengulang semua data yang diterima dan untuk masing-masing, kami menampilkan informasinya. Kita dapat menggunakan 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 :style="{ backgroundImage: `url(${video.poster})` }" 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 merutekan 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 menyetel 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=" http://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>

Saat kami mengklik salah satu video, kami mendapatkan:

Menambahkan File Caption Kami

Untuk menambahkan file track kami, kami memastikan semua file .vtt di folder captions memiliki nama yang sama dengan id kami. Perbarui elemen video kami dengan trek, membuat permintaan untuk teksnya.

 <template> <div class="player"> <video controls muted autoPlay crossOrigin="anonymous"> <source :src=" http://localhost:5000/videos/video/${vidName} " type="video/mp4"> <track label="English" kind="captions" srcLang="en" :src=" http://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 Tangguh.

Saat membuat aplikasi streaming seperti Twitch, Hulu, atau Netflix, ada beberapa hal yang harus dipertimbangkan:

  • Pipa pemrosesan data video
    Ini bisa menjadi tantangan teknis karena server berperforma tinggi dibutuhkan untuk menyajikan jutaan video kepada pengguna. Latensi tinggi atau waktu henti harus dihindari dengan segala cara.
  • Caching
    Mekanisme cache harus digunakan saat membangun jenis aplikasi contoh Cassandra, Amazon S3, AWS SimpleDB.
  • Geografi pengguna
    Mempertimbangkan geografi pengguna Anda harus dipikirkan untuk distribusinya.

Kesimpulan

Dalam tutorial ini, kita telah melihat cara membuat server di Node.js yang mengalirkan video, menghasilkan 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 Anda dan membuat Anda menyusun aplikasi dengan lebih baik.

Sumber daya

April 13, 2021

codeorayo

Ampuh! Ini rahasia mengembangkan aplikasi secara instan, tinggal download dan kembangkan. Gabung sekarang juga! Premium Membership [PRIVATE] https://premium.codeorayo.com

Leave a Reply

Your email address will not be published. Required fields are marked *