Nuxt.js, Node ve Express ile Video Akış Uygulaması Oluşturma
Yayınlanan: 2022-03-10Videolar akışlarla çalışır. Bu, tüm videoyu bir kerede göndermek yerine, videonun tamamını oluşturan daha küçük parçalar kümesi olarak gönderildiği anlamına gelir. Bu, yavaş geniş bantta bir video izlerken videoların neden arabelleğe alındığını açıklar, çünkü yalnızca aldığı parçaları oynatır ve daha fazlasını yüklemeye çalışır.
Bu makale, gerçek bir proje oluşturarak yeni bir teknoloji öğrenmek isteyen geliştiriciler içindir: arka uç olarak Node.js ve istemci olarak Nuxt.js olan bir video akışı uygulaması.
- Node.js , hızlı ve ölçeklenebilir uygulamalar oluşturmak için kullanılan bir çalışma zamanıdır. Bunu, videoları getirme ve yayınlama, videolar için küçük resimler oluşturma ve videolar için altyazı ve altyazı sunma işlemlerini gerçekleştirmek için kullanacağız.
- Nuxt.js , sunucu tarafından oluşturulan Vue.js uygulamalarını kolayca oluşturmamıza yardımcı olan bir Vue.js çerçevesidir. Videolar için API'mizi kullanacağız ve bu uygulamanın iki görünümü olacaktır: mevcut videoların bir listesi ve her video için bir oynatıcı görünümü.
Önkoşullar
- HTML, CSS, JavaScript, Node/Express ve Vue.js anlama.
- Bir metin düzenleyici (örn. VS Kodu).
- Bir web tarayıcısı (ör. Chrome, Firefox).
- İş istasyonunuzda yüklü FFmpeg.
- Düğüm.js. nvm.
- Kaynak koduna GitHub'dan ulaşabilirsiniz.
Uygulamamızı Kurma
Bu uygulamada, ön uçtan istek yapmak için rotalar oluşturacağız:
-
videos
, videoların ve verilerinin bir listesini almak için yönlendirilir. - video listemizden yalnızca bir video almak için bir yol.
- videoları yayınlamak için
streaming
yolu. -
captions
izlediğimiz videolara altyazı eklemek için rota.
Rotalarımız oluşturulduktan sonra, Home
ve dinamik player
sayfasını oluşturacağımız Nuxt
oluşturacağız. Ardından, ana sayfayı video verileriyle doldurmak için video rotamızı, player
sayfamızdaki videos
yayınlamak için başka bir istek ve son olarak videolar tarafından kullanılacak altyazı dosyalarını sunmak için bir istek istiyoruz.
Uygulamamızı kurmak için proje dizinimizi oluşturuyoruz,
mkdir streaming-app
Sunucumuzu Kurmak
streaming-app
dizinimizde backend
adında bir klasör oluşturuyoruz.
cd streaming-app mkdir backend
Arka uç klasörümüzde, sunucu projemiz hakkında bilgi depolamak için bir package.json
dosyası başlatıyoruz.
cd backend npm init -y
uygulamamızı oluşturmak için aşağıdaki paketleri kurmamız gerekiyor.
-
nodemon
, değişiklik yaptığımızda sunucumuzu otomatik olarak yeniden başlatır. -
express
, rotaları işlemek için bize güzel bir arayüz sağlar. -
cors
, istemcimiz ve sunucumuz farklı bağlantı noktalarında çalışacağından, çapraz kaynaklı isteklerde bulunmamıza izin verecek.
Arka uç dizinimizde, videolarımızı akış için tutmak için bir klasör assets
oluşturuyoruz.
mkdir assets
Bir .mp4
dosyasını varlıklar klasörüne kopyalayın ve adını video1
olarak adlandırın. Github Repo'da bulabileceğiniz .mp4
kısa örnek videoları kullanabilirsiniz.
Bir app.js
dosyası oluşturun ve uygulamamız için gerekli paketleri ekleyin.
const express = require('express'); const fs = require('fs'); const cors = require('cors'); const path = require('path'); const app = express(); app.use(cors())
fs
modülü, sunucumuzdaki dosyaları kolayca okumak ve bunlara yazmak için kullanılırken, path
modülü, dizinler ve dosya yollarıyla çalışmanın bir yolunu sağlar.
Şimdi bir ./video
rotası oluşturuyoruz. İstendiğinde, istemciye bir video dosyası gönderir.
// add after 'const app = express();' app.get('/video', (req, res) => { res.sendFile('assets/video1.mp4', { root: __dirname }); });
Bu rota, istendiğinde video1.mp4
video dosyasını sunar. Daha sonra 3000
portundaki sunucumuzu dinliyoruz.
// add to end of app.js file app.listen(5000, () => { console.log('Listening on port 5000!') });
Sunucumuzu nodemon kullanarak başlatmak için package.json
dosyasına bir komut dosyası eklenir.
"scripts": { "start": "nodemon app.js" },
Ardından terminal çalıştırmanızda:
npm run start
Listening on port 3000!
terminalde, sunucu düzgün çalışıyor. Tarayıcınızda https://localhost:5000/video adresine gidin ve videonun oynatıldığını görmelisiniz.
Ön Uç Tarafından İşlenecek İstekler
Sunucunun işlemesi için ihtiyaç duyduğumuz ön ucumuzdan arka uca yapacağımız istekler aşağıdadır.
-
/videos
Ön ucumuzdakiHome
sayfadaki video listesini doldurmak için kullanılacak bir dizi video maket verisi döndürür. -
/video/:id/data
Tek bir video için meta verileri döndürür. ÖnyüzümüzdekiPlayer
sayfası tarafından kullanılır. -
/video/:id
Belirli bir kimliğe sahip bir video akışı yapar.Player
sayfası tarafından kullanılır.
Rotaları oluşturalım.
Video Listesi İçin Model Verilerini Döndür
Bu demo uygulaması için, meta verileri tutacak ve istendiğinde bunu ön uca gönderecek bir dizi nesne oluşturacağız. Gerçek bir uygulamada, muhtemelen daha sonra bunun gibi bir dizi oluşturmak için kullanılacak olan bir veritabanındaki verileri okuyor olacaksınız. Basitlik adına, bu eğitimde bunu yapmayacağız.
Arka uç klasörümüzde bir mockdata.js
dosyası oluşturun ve onu video listemiz için meta verilerle doldurun.
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
Yukarıdan görebiliriz, her nesne video hakkında bilgi içerir. Videonun poster görüntüsünün bağlantısını içeren poster
özelliğine dikkat edin.
Frontend tarafından yapılacak tüm isteklerimizin başına /videos
eklendiğinden bir videos
rotası oluşturalım.
Bunu yapmak için bir routes
klasörü oluşturalım ve /videos
route'umuz için bir Video.js
dosyası ekleyelim. Bu dosyada, rotamızı oluşturmak için eksprese ihtiyacımız olacak ve express
yönlendiriciyi kullanacağız.
const express = require('express') const router = express.Router()
/videos
rotasına gittiğimizde video listemizi almak istiyoruz, bu yüzden mockData.js
dosyasını Video.js
dosyamıza gerektirelim ve talebimizi yapalım.
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;
/videos
rotası şimdi bildirildi, dosyayı kaydedin ve sunucuyu otomatik olarak yeniden başlatması gerekir. Başladıktan sonra, https://localhost:3000/videos'a gidin ve dizimiz JSON biçiminde döndürülür.
Tek Bir Video İçin Verileri Döndür
Video listemizdeki belirli bir video için istekte bulunabilmek istiyoruz. Verdiğimiz id
kullanarak dizimizdeki belirli bir video verisini getirebiliriz. Hala Video.js
dosyamızda bir istekte bulunalım.
// make request for a particular video router.get('/:id/data', (req,res)=> { const id = parseInt(req.params.id, 10) res.json(videos[id]) })
Yukarıdaki kod, rota parametrelerinden id
alır ve onu bir tam sayıya dönüştürür. Ardından, videos
dizisinden id
ile eşleşen nesneyi istemciye geri göndeririz.
Videoların Akışı
app.js
dosyamızda, istemciye bir video sunan bir /video
yolu oluşturduk. Bu uç noktanın, istek üzerine bir video dosyasının tamamını sunmak yerine, videonun daha küçük parçalarını göndermesini istiyoruz.
allVideos
dizisindeki üç videodan birini dinamik olarak sunabilmek ve videoları parçalar halinde yayınlayabilmek istiyoruz, bu nedenle:
/video
yolunu app.js
.
Üç videoya ihtiyacımız var, bu nedenle öğreticinin kaynak kodundaki örnek videoları server
projenizin assets/
dizinine kopyalayın. Videoların dosya adlarının, videos
dizisindeki id
karşılık geldiğinden emin olun:
Video.js
dosyamıza döndüğünüzde, akış videoları için rota oluşturun.
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); } });
Tarayıcımızda https://localhost:5000/videos/video/outside-the-wire adresine gidersek video akışını görebiliriz.
Akış Video Rotası Nasıl Çalışır?
Akış video rotamızda yazılmış oldukça fazla kod var, o yüzden satır satır inceleyelim.
const videoPath = `assets/${req.params.id}.mp4`; const videoStat = fs.statSync(videoPath); const fileSize = videoStat.size; const videoRange = req.headers.range;
İlk olarak, isteğimizden, req.params.id
kullanarak rotadan id
alıyoruz ve bunu videonun videoPath
oluşturmak için kullanıyoruz. Daha sonra import ettiğimiz fs
dosya sistemini kullanarak fileSize
dosyasını okuruz. Videolar için, kullanıcının tarayıcısı istekte bir range
parametresi gönderir. Bu, sunucunun videonun hangi parçasının istemciye geri gönderileceğini bilmesini sağlar.
Bazı tarayıcılar ilk istekte bir aralık gönderir, ancak diğerleri göndermez. Göndermeyenler için veya başka bir nedenle tarayıcı bir aralık göndermezse, bunu else
bloğunda ele alırız. Bu kod dosya boyutunu alır ve videonun ilk birkaç parçasını gönderir:
else { const head = { 'Content-Length': fileSize, 'Content-Type': 'video/mp4', }; res.writeHead(200, head); fs.createReadStream(path).pipe(res); }
Bir if
bloğundaki aralık da dahil olmak üzere sonraki istekleri ele alacağız.
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); }
Yukarıdaki bu kod, aralığın start
ve end
değerlerini kullanarak bir okuma akışı oluşturur. Yanıt başlıklarının Content-Length
değerini, start
ve end
değerlerinden hesaplanan yığın boyutuna ayarlayın. Ayrıca, yanıtın kısmi içerik içerdiğini belirten HTTP kodu 206'yı kullanırız. Bu, tarayıcının videonun tüm parçalarını alana kadar istekte bulunmaya devam edeceği anlamına gelir.
Kararsız Bağlantılarda Ne Olur?
Kullanıcı yavaş bir bağlantıdaysa, ağ akışı, istemci daha fazla veri için hazır olana kadar G/Ç kaynağının duraklatılmasını isteyerek bunu bildirir. Bu geri basınç olarak bilinir. Bu örneği bir adım daha ileri götürebilir ve akışı genişletmenin ne kadar kolay olduğunu görebiliriz. Kolayca sıkıştırma da ekleyebiliriz!
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});
Yukarıda bir ReadStream
oluşturulduğunu ve videoyu parça parça sunduğunu görebiliriz.
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);
İstek başlığı, bir sonraki video parçasının ön uçta akışını sağlamak için başlangıç ve bitiş değişikliği olan Content-Range
öğesini içerir; content-length
, gönderilen videonun parçasıdır. Ayrıca, izlediğimiz içeriğin türünü mp4
olarak belirtiyoruz. 206'nın yazma kafası, yalnızca yeni yapılan akışlarla yanıt verecek şekilde ayarlanmıştır.
Videolarımız İçin Altyazı Dosyası Oluşturma
Bir .vtt
altyazı dosyası böyle görünür.
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.
Altyazı dosyaları, bir videoda söylenenler için metin içerir. Ayrıca, metnin her satırının ne zaman görüntülenmesi gerektiğine ilişkin zaman kodlarını da içerir. Videolarımızın altyazıları olmasını istiyoruz ve bu eğitim için kendi altyazı dosyamızı oluşturmayacağız, böylece depodaki assets
dizinindeki altyazılar klasörüne gidebilir ve altyazıları indirebilirsiniz.
Altyazı isteğini işleyecek yeni bir rota oluşturalım:
router.get('/video/:id/caption', (req, res) => res.sendFile(`assets/captions/${req.params.id}.vtt`, { root: __dirname }));
Ön Uçumuzu Oluşturmak
Sistemimizin görsel kısmına başlamak için ön uç iskelemizi kurmamız gerekecekti.
Not : Uygulamamızı oluşturmak için vue-cli'ye ihtiyacınız var. Bilgisayarınızda yüklü değilse, npm install -g @vue/cli
çalıştırabilirsiniz.
Kurulum
Projemizin kökünde ön uç klasörümüzü oluşturalım:
mkdir frontend cd frontend
ve içinde package.json
dosyamızı başlatıyoruz, aşağıdakini kopyalayıp içine yapıştırıyoruz:
{ "name": "my-app", "scripts": { "dev": "nuxt", "build": "nuxt build", "generate": "nuxt generate", "start": "nuxt start" } }
sonra nuxt
kurun:
npm add nuxt
ve Nuxt.js uygulamasını çalıştırmak için aşağıdaki komutu yürütün:
npm run dev
Nuxt Dosya Yapımız
Nuxt'u yüklediğimize göre artık önyüzümüzü oluşturmaya başlayabiliriz.
Öncelikle, uygulamamızın kökünde bir layouts
klasörü oluşturmamız gerekiyor. Bu klasör, hangi sayfaya gidersek gidelim uygulamanın düzenini tanımlar. Gezinme çubuğumuz ve alt bilgimiz gibi şeyler burada bulunur. Frontend klasöründe, frontend uygulamamızı başlattığımızda varsayılan mizanpajımız için default.vue
oluşturuyoruz.
mkdir layouts cd layouts touch default.vue
Ardından tüm bileşenlerimizi oluşturmak için bir components
klasörü. Yalnızca iki bileşene ihtiyacımız olacak, NavBar
ve video
bileşeni. Yani ön uç kök klasörümüzde:
mkdir components cd components touch NavBar.vue touch Video.vue
Son olarak, home
ve about
gibi tüm sayfalarımızın oluşturulabileceği bir sayfalar klasörü oluşturulabilir. Bu uygulamada ihtiyacımız olan iki sayfa, tüm videolarımızı ve video bilgilerimizi gösteren home
sayfa ve tıkladığımız videoya yönlendiren dinamik bir oynatıcı sayfasıdır.
mkdir pages cd pages touch index.vue mkdir player cd player touch _name.vue
Ön uç dizinimiz şimdi şöyle görünüyor:
|-frontend |-components |-NavBar.vue |-Video.vue |-layouts |-default.vue |-pages |-index.vue |-player |-_name.vue |-package.json |-yarn.lock
Navbar Bileşeni
NavBar.vue
şöyle görünür:
<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
, biraz stil ile Streaming App öğesini görüntüleyen bir h1
etiketine sahiptir.
NavBar
default.vue
düzenimize aktaralım.
// default.vue <template> <div> <NavBar /> <nuxt /> </div> </template> <script> import NavBar from "@/components/NavBar.vue" export default { components: { NavBar, } } </script>
default.vue
düzeni artık NavBar
bileşenimizi ve oluşturduğumuz herhangi bir sayfanın nerede görüntüleneceğini belirten <nuxt />
etiketini içeriyor.
Ana sayfamız olan index.vue
, tüm videoları sunucumuzdan almak için https://localhost:5000/videos
için istekte bulunalım. Verileri daha sonra oluşturacağımız video.vue
bileşenimize prop olarak iletmek. Ama şimdilik, onu zaten ithal ettik.
<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>
Video Bileşeni
Aşağıda, önce prop'umuzu ilan ediyoruz. Video verileri artık bileşende mevcut olduğundan, Vue'nun v-for
for'unu kullanarak alınan tüm veriler üzerinde yineleniriz ve her biri için bilgileri görüntüleriz. Veriler arasında dolaşmak ve onu bir liste olarak görüntülemek v-for
yönergesini kullanabiliriz. Bazı temel stiller de eklendi.
<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>
NuxtLink
/player/video.id
yönlendirme yapan dinamik bir rotaya sahip olduğunu da fark ettik.
İstediğimiz işlevsellik, bir kullanıcı videolardan herhangi birini tıkladığında akışa başlar. Bunu başarmak için _name.vue
yolunun dinamik yapısından yararlanıyoruz.
İçinde, bir video oynatıcı oluşturuyoruz ve video akışı için kaynağı uç noktamıza ayarladık, ancak bağlantının hangi parametreyi aldığını yakalayan this.$route.params.name
yardımıyla son noktamıza hangi videoyu oynatacağımızı dinamik olarak ekleriz. .
<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>
Videolardan herhangi birine tıkladığımızda şunu elde ederiz:
Altyazı Dosyamızı Ekleme
Parça dosyamızı eklemek için, altyazılar klasöründeki tüm .vtt
dosyalarının kimliğimizle aynı ada sahip olduğundan emin id
. Altyazılar için istekte bulunarak video öğemizi parçayla güncelleyin.
<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>
Video öğesine crossOrigin="anonymous"
ekledik; aksi takdirde altyazı isteği başarısız olur. Şimdi yenileyin ve altyazıların başarıyla eklendiğini göreceksiniz.
Esnek Video Akışı Oluştururken Akılda Tutulması Gerekenler.
Twitch, Hulu veya Netflix gibi akış uygulamaları oluştururken dikkate alınan birkaç şey vardır:
- Video veri işleme hattı
Kullanıcılara milyonlarca video sunmak için yüksek performanslı sunuculara ihtiyaç duyulduğundan, bu teknik bir zorluk olabilir. Her ne pahasına olursa olsun yüksek gecikme veya kesinti süresinden kaçınılmalıdır. - Önbelleğe almak
Bu tür bir uygulama örneği oluştururken önbelleğe alma mekanizmaları kullanılmalıdır; Cassandra, Amazon S3, AWS SimpleDB. - Kullanıcıların coğrafyası
Kullanıcılarınızın coğrafyası göz önünde bulundurularak dağıtım için düşünülmelidir.
Çözüm
Bu öğreticide, Node.js'de video akışı yapan, bu videolar için altyazı oluşturan ve videoların meta verilerini sunan bir sunucunun nasıl oluşturulacağını gördük. Ayrıca, uç noktaları ve sunucu tarafından oluşturulan verileri kullanmak için Nuxt.js'nin ön uçta nasıl kullanılacağını gördük.
Diğer çerçevelerin aksine Nuxt.js ve Express.js ile uygulama oluşturmak oldukça kolay ve hızlıdır. Nuxt.js'nin güzel yanı, rotalarınızı yönetme ve uygulamalarınızı daha iyi yapılandırmanıza olanak sağlamasıdır.
- Nuxt.js hakkında daha fazla bilgiyi buradan edinebilirsiniz.
- Kaynak koduna Github üzerinden ulaşabilirsiniz.
Kaynaklar
- "HTML5 Videoya Altyazılar ve Altyazılar Ekleme", MDN Web Dokümanları
- “Alt Yazıları ve Alt Yazıları Anlama,” Screenfont.ca