Nuxt.js, Node ve Express ile Video Akış Uygulaması Oluşturma

Yayınlanan: 2022-03-10
Kısa özet ↬ Bu makalede, Nuxt.js ve Node.js kullanarak bir video akışı uygulaması oluşturacağız. Spesifik olarak, videoları getirme ve yayınlama, videolarınız için küçük resimler oluşturma ve altyazı ve altyazı sunma işlemlerini gerçekleştirecek bir sunucu tarafı Node.js uygulaması oluşturacağız.

Videolar 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
Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓

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 ucumuzdaki Home 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üzdeki Player 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:

Nuxt video akışı uygulaması nihai sonucu
Kullanıcı küçük resmi tıkladığında video akışı başlar. (Büyük önizleme)

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