// src/controllers/episodesController.ts
import { Request, Response } from 'express'
import fs from 'fs'
import path from 'path'
export const episodesController = {
// GET /episodes/stream
stream: async (req: Request, res: Response) => {
const { videoUrl } = req.query
}
}
// src/controllers/episodesController.ts
import { Request, Response } from 'express'
import fs from 'fs'
import path from 'path'
export const episodesController = {
// GET /episodes/stream
stream: async (req: Request, res: Response) => {
const { videoUrl } = req.body
try {
if (typeof videoUrl !== 'string') {
throw new Error('videoUrl deve ser do tipo \\'string\\'');
}
const filePath = path.join(__dirname, '../../uploads', videoUrl)
const fileStat = fs.statSync(filePath)
const range = req.headers.range
} catch (err) {
if (err instanceof Error) {
return res.status(400).json({ message: err.message })
}
}
}
}
// src/controllers/episodesController.ts
// ...
try {
if (typeof videoUrl !== 'string') {
throw new Error('videoUrl must be of type \\'string\\'');
}
const filePath = path.join(__dirname, '../../uploads', videoUrl)
const fileStat = fs.statSync(filePath)
const range = req.headers.range
if (range) {
const parts = range.replace(/bytes=/, '').split('-')
const start = parseInt(parts[0], 10)
const end = parts[1] ? parseInt(parts[1], 10) : fileStat.size - 1
const chunkSize = (end - start) + 1
} else {
}
} catch (err) {
// ...
// src/controllers/episodesController.ts
// ...
if (range) {
const parts = range.replace(/bytes=/, '').split('-')
const start = parseInt(parts[0], 10)
const end = parts[1] ? parseInt(parts[1], 10) : fileStat.size - 1
const chunkSize = (end - start) + 1
const file = fs.createReadStream(filePath, { start, end })
const head = {
'Content-Range': `bytes ${start}-${end}/${fileStat.size}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize,
'Content-Type': 'video/mp4',
}
res.writeHead(206, head)
file.pipe(res)
} else {
}
} catch (err) {
// ...
// src/controllers/episodesController.ts
// ...
file.pipe(res)
} else {
const head = {
'Content-Length': fileStat.size,
'Content-Type': 'video/mp4',
}
res.writeHead(200, head)
fs.createReadStream(filePath).pipe(res)
}
} catch (err) {
// ...
// public/video.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Comp atible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Testando Streaming</title>
</head>
<body>
<h2>Testando Streaming</h2>
<video id="videoplayer" controls>
<source src="/episodes/stream?videoUrl=videos/course-1/configuracao-obs-canvas.mp4" type="video/mp4">
</video>
<script>
var myvideo = document.getElementById('videoplayer')
myvideo.currentTime = 70;
myvideo.play();
</script>
</body>
</html>
O caminho foi inserido manualmente por se tratar de um exemplo. Ao navegar até http://localhost:3000/video.html é possível ver o player reproduzindo o vídeo e o seu conteúdo parcialmente carregado na barra inferior de controles.
Antes de terminar podemos refatorar o controlador, movendo a maior parte do método para um serviço. Para isso vamos criar o arquivo episodeService.ts na pasta services. Dentro dele podemos adicionar o seguinte conteúdo:
// src/services/episodeService.ts
import { Response } from 'express'
import fs from 'fs'
import path from 'path'
import { WatchTime } from '../models'
export const episodeService = {
streamEpisodeToResponse: (res: Response, videoUrl: string, range: string | undefined) => {
const filePath = path.join(__dirname, '../../uploads', videoUrl)
const fileStat = fs.statSync(filePath)
if (range) {
const parts = range.replace(/bytes=/, '').split('-')
const start = parseInt(parts[0], 10)
const end = parts[1] ? parseInt(parts[1], 10) : fileStat.size - 1
const chunkSize = (end - start) + 1
const file = fs.createReadStream(filePath, { start, end })
const head = {
'Content-Range': `bytes ${start}-${end}/${fileStat.size}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize,
'Content-Type': 'video/mp4',
}
res.writeHead(206, head)
file.pipe(res)
} else {
const head = {
'Content-Length': fileStat.size,
'Content-Type': 'video/mp4',
}
res.writeHead(200, head)
fs.createReadStream(filePath).pipe(res)
}
},
}
Agora no controlador episodesController.ts podemos deixar apenas a chamada a este novo método:
// src/controllers/episodesController.ts
import { Request, Response } from 'express'
import { episodeService } from '../services/episodeService'
export const episodesController = {
// GET /episodes/stream
stream: async (req: Request, res: Response) => {
const { videoUrl } = req.query
const range = req.headers.range
try {
if (typeof videoUrl !== 'string') {
throw new Error('videoUrl must be of type \\'string\\'');
}
episodeService.streamEpisodeToResponse(res, videoUrl, range)
} catch (err) {
if (err instanceof Error) {
return res.status(400).json({ message: err.message })
}
}
},
}