Як створити відновляється відео-завантажувач в Node.js

318

Від автора: якщо ви коли-небудь викладали порівняно великий відео файл, то відчували це відчуття: закінчили вже на 90% і ненавмисно оновили сторінку – і доводиться починати все знову.

У цьому уроці я покажу, як зробити відео-завантажувач для свого сайту, вміє відновлювати перервану завантаження, а по її завершенні – генерувати піктограму.

Як створити відновляється відео-завантажувач в Node.js

Вступ

Щоб зробити завантажувач відновлюваним, сервера потрібно відстежити, який обсяг файлу вже вивантажений, і вміти продовжити з того місця, де вивантаження зупинилася. Для виконання цього завдання ми дамо сервера Node.js повний контроль над запитами окремих блоків даних, а форма HTML буде зчитувати ці запити і посилати серверу необхідну інформацію.

Для створення такого обміну застосуємо Socket.io. Якщо ви ніколи не чули про Socket.io, то це інфраструктура для комунікації в режимі реального часу між Node.js і веб-сторінкою HTML – більш повно про це ми незабаром поговоримо.
Такий загальний задум; почнемо з форми HTML.

Крок 1: HTML

Я збираюся зробити HTML простим і ясним; все, що нам потрібно – enter для вибору файлу, текстове поле для назви і кнопка початку вивантаження. Ось необхідний код:

Video Uploader

Choose A File:
Name:
Upload

Зверніть увагу, що я обернув вміст у span; цим ми скористаємося пізніше для оновлення розмітки сторінок за допомогою JavaScript. У цьому підручнику я не збираюся розжовувати весь CSS, а ви, якщо захочете скористатися моїм вихідним кодом, можете його скачати.

Як створити відновляється відео-завантажувач в Node.js

Крок 2: Примусимо його працювати

HTML5 ще порівняно новий, і не підтримується всіма браузерами. Перше, що потрібно зробити – це переконатися, що браузер підтримує HTML5 File API і клас FileReader.

Клас FileReader дає можливість відкривати і читати частини файлу і передавати дані на сервер як двійкову рядок. Ось JavaScript для детекції контурів:

window.addEventListener. (“load”, Ready);
function Ready(){
if(window.File && window.FileReader){ //These are the relevant HTML5 objects that we are going to use
document.getElementById(‘UploadButton’).addEventListener. (‘click’, StartUpload);
document.getElementById(‘FileBox’).addEventListener. (‘change’, FileChosen);
}
else
{
document.getElementById(‘UploadArea’).innerHTML = “Your Browser doesn’t Support The File API Please Update Your Browser”;
}
}

Вищенаведений код додає кнопці і введення файлу у форму обробники подій. Функція FileChosen просто встановлює глобальну змінну файлу – щоб пізніше ми змогли отримати до нього доступ і заповнює поле вводу назви, щоб у користувача була контрольна точка при присвоєння імені файлу. Ось функція FileChosen:

var SelectedFile;
function FileChosen(evnt) {
SelectedFile = evnt.target.files[0];
document.getElementById(‘NameBox’).value = SelectedFile.name;
}

Перед написанням функції StartUpload потрібно встановити Node.js з socket.io; давайте цим і займемося.

Крок 3: Сервер Socket.io

Як згадувалося раніше, я застосую Socket.io для комунікації між сервером і файлом HTML. Для закачування Socket.io напишіть npm install socket.io у вікно терміналу (мається на увазі, що Node.js ви встановили), перейшовши в директорію цього проекту. Socket.io працює так: як тільки сервер або клієнт «емітує» подія, інша сторона вловлює цю подію у вигляді функції з параметром передачі даних JSON туди і назад. Для початку створіть пустий файл JavaScript і помістіть в нього наступний код.

var app = require(‘http’).createServer(handler)
, io = require(‘socket.io’).listen(app)
, fs = require(‘fs’)
, exec = require(‘child_process’).exec
, util = require(‘util’)
app.listen(8080);
function handler (req, res) {
fs.readFile(__dirname + ‘/index.html’,
function (err, data) {
if (err) {
res.writeHead(500);
return res.end(‘Error loading index.html’);
}
res.writeHead(200);
res.end(data);
});
}
io.sockets.on(‘connection’, function (socket) {
//Events will go here
});

Перші п’ять рядків включають потрібні бібліотеки, наступний рядок передає серверу інструкцію слухати порт 8080, а функція обробника просто передає вміст нашого файлу HTML користувачеві при вході на сайт.

Останні два рядки – це оброблювач socket.io, і викликаються, коли хтось з’єднується через Socket.io.
Тепер можна повернутися до файлу HTML і визначити кілька подій socket.io.

Крок 4: Події Socket.io

Щоб почати використовувати на своїй сторінці Socket.io, спочатку потрібно з’єднатися з його бібліотекою JavaScript. Робимо це таким же чином, яким ми звернулися до будь бібліотеці: зробіть посилання на неї в області head. Додайте на сторінку наступне, природно, перед своїми скриптами.

Не турбуйтеся з приводу отримання цього файлу, так як він генерується сервером Node.js під час виконання. Тепер можна написати функцію StartUpload, яку ми приєднали до своєї кнопку:

var socket = io.connect(‘http://localhost:8080’);
var FReader;
var Name;
function StartUpload(){
if(document.getElementById(‘FileBox’).value != “”)
{
FReader = new FileReader();
Name = document.getElementById(‘NameBox’).value;
var Content = “Uploading ” + SelectedFile.name + ” as ” + Name + ““;
Content += ‘

0%‘;
Content += “0/” + Math.round(SelectedFile.size / 1048576) + “MB“;
document.getElementById(‘UploadArea’).innerHTML = Content;
FReader.onload = function(evnt){
socket.emit(‘Upload’, { ‘Name’ : Name, Data : evnt.target.result });
}
socket.emit(‘Start’, { ‘Name’ : Name, ‘Size’ : SelectedFile.size });
}
else
{
alert(“Please Select A File”);
}
}

Перший рядок з’єднує з сервером Socket.io; далі ми створили дві змінні для File Reader’а і назву файлу, так як збираємося отримати до них глобальний доступ. Усередині цієї функції ми спочатку переконалися, що користувач вибрав файл, і, якщо він це зробив, створюємо FileReader і оновлюємо DOM з гарним індикатором виконання.

Метод FileReader’а onload викликається кожного разу при зчитуванні даних; все, що потрібно зробити – це емітувати подія Upload і послати дані на сервер. Нарешті, эмитируем подія Start, передаючи сервера Node.js назва і розмір файлу.

Тепер повернемося до файлу Node.js і виконаємо до цих двох подій обробники.

Крок 5: Обробка подій

Вам потрібно час від часу очищати буфер, або сервер впаде через перевантаження пам’яті.

Події socket.io проходять всередині обробника, що знаходиться в останньому рядку файлу Node.js. Перша подія, яку ми виконаємо – Start, що запускається при натисканні користувача по кнопці Upload (Вивантажити).

Раніше я згадував, що сервер повинен контролювати ті дані, які йому потрібно отримати далі; це дозволить продовжувати закачування з попередньою, залишилася незавершеною. Він робить це, спочатку визначивши, знаходився там не закінчив вивантаження файл з такою назвою, і якщо був, то продовжить з місця, де той зупинився; інакше почне все спочатку. Ми передамо ці дані в инкрементах за полмегабайта, що виходить за 524288 байтів.

Для відстеження різних выгрузок, що відбуваються одночасно, нам потрібно додати змінну для зберігання. Вгорі свого файлу додайте var Files = {};’ Ось код події Start:

socket.on(‘Start’, function (data) { //data contains the variables that we passed through in the html file
var Name = data[‘Name’];
Files[Name] = { //Create a new Entry in The Files Variable
FileSize : data[‘Size’],
Data : “”,
Downloaded : 0
}
var Place = 0;
try{
var Stat = fs.statSync(‘Temp/’ + Name);
if(Stat.isFile())
{
Files[Name][‘Downloaded’] = Stat.size;
Place = Stat.size / 524288;
}
}
catch(er){} //it’s a New File
fs.open(“Temp/” + Name, “a”, 0755, function(err, fd){
if(err)
{
console.log(err);
}
else
{
Files[Name][‘Handler’] = fd; //We store the file handler so we can write to it later
socket.emit(‘MoreData’, { ‘Place’ : Place, Percent : 0 });
}
});
});

Спочатку додаємо в масив Files новий файл з розміром, даними і кількістю закачаних до цього моменту байтів. Змінна Place зберігає, де ми знаходимося у файлі – вона по замовчуванням ставиться на 0, що означає початок. Потім перевіряємо, чи вже існує файл (тобто він був уже на середині і зупинився), і відповідно оновлюємо змінні. Нова чи це викачка чи ні, зараз ми відкриваємо файл для запису в теку Temp/ і эмитируем подія MoreData, щоб запросити на наступну частину даних з файлу HTML.

Тепер потрібно додати подію Upload, який, якщо ви пам’ятаєте, викликається кожного разу, коли прочитується новий блок даних. Ось функція:

socket.on(‘Upload’, function (data){
var Name = data[‘Name’];
Files[Name][‘Downloaded’] += data[‘Data’].length;
Files[Name][‘Data’] += data[‘Data’];
if(Files[Name][‘Downloaded’] == Files[Name][‘FileSize’]) //If File is Fully Uploaded
{
fs.write(Files[Name][‘Handler’], Files[Name][‘Data’], null, ‘Binary’, function(err, Writen){
//Get Thumbnail Here
});
}
else if(Files[Name][‘Data’].length > 10485760){ //If the Data Buffer reaches 10MB
fs.write(Files[Name][‘Handler’], Files[Name][‘Data’], null, ‘Binary’, function(err, Writen){
Files[Name][‘Data’] = “”; //Reset The Buffer
var Place = Files[Name][‘Downloaded’] / 524288;
var Percent = (Files[Name][‘Downloaded’] / Files[Name][‘FileSize’]) * 100;
socket.emit(‘MoreData’, { ‘Place’ : Place, ‘Percent’ : Percent});
});
}
else
{
var Place = Files[Name][‘Downloaded’] / 524288;
var Percent = (Files[Name][‘Downloaded’] / Files[Name][‘FileSize’]) * 100;
socket.emit(‘MoreData’, { ‘Place’ : Place, ‘Percent’ : Percent});
}
});

Дві перші рядки цього коду оновлюють новими даними буфер і змінну загальної кількості завантажених байтів. Нам потрібно зберігати дані в буфері і зберігати їх в инкрементах, щоб не покласти сервер через перевантаження пам’яті; кожні десять мегабайт ми будемо зберігатися і чистити буфер.

Перше if-пропозиція визначає, повністю чи выкачался файл, друге перевіряє, чи досяг розмір буфера 10 MB і, нарешті, ми запитуємо MoreData, передаючи виконання у відсотках і витягуючи наступний блок даних.

Тепер можна повернутися до файлу HTML, виконати подія MoreData і оновити прогрес.

Крок 6: Відстеження прогресу

Я створив функцію оновлення індикатора виконання і кількості викачаних на сторінку мегабайт. Додатково до цього подія More Data зчитує блок запитаних сервером даних і передає їх на нього.

Щоб розбити файл на блоки, ми застосовуємо команду File API Slice. Так як File API все ще розробляється, нам потрібно застосувати для браузерів Webkit і Mozilla відповідно webkitSlice і mozSlice.

socket.on(‘MoreData’, function (data){
UpdateBar(data[‘Percent’]);
var Place = data[‘Place’] * 524288; //The Next Blocks Starting Position
var NewFile; //The Variable that will hold the new Block of Data
if(SelectedFile.webkitSlice)
NewFile = SelectedFile.webkitSlice(Place, Place + Math.min(524288, (SelectedFile.size-Place)));
else
NewFile = SelectedFile.mozSlice(Place, Place + Math.min(524288, (SelectedFile.size-Place)));
FReader.readAsBinaryString(NewFile);
});
function UpdateBar(percent){
document.getElementById(‘ProgressBar’).style.width = percent + ‘%’;
document.getElementById(‘percent’).innerHTML = (Math.round(percent*100)/100) + ‘%’;
var MBDone = Math.round(((percent/100.0) * SelectedFile.size) / 1048576);
document.getElementById(‘MB’).innerHTML = MBDone;
}

На цій функції завантажувач нарешті закінчується! Все, що нам залишається зробити – перемістити закінчений файл з папки Temp/ і згенерувати піктограму.

Як створити відновляється відео-завантажувач в Node.js

Крок 7: Піктограма

Перед генеруванням піктограми потрібно перемістити файл з тимчасової папки. Це можна зробити, застосувавши файлові потоки і метод pump. Метод pump приймає потоки read і write і буферизує дані. Вам потрібно додати цей код туди, де в події Upload я написав ‘Тут піктограма’:

var inp = fs.createReadStream (Temp/” + Name);
var out = fs.createWriteStream(“Video/” + Name);
util.pump(inp, out, function(){
fs.unlink (Temp/” + Name, function () { //This Deletes The Temporary File
//Moving File Completed
});
});

Ми додали команду разъединиться unlink; так тимчасовий файл піде після закінчення його копіювання. Тепер перейдемо до піктограмі: для генерації піктограм ми застосуємо ffmpeg, тому що він здатний обробляти безліч форматів і його легко встановити. На момент написання цього тексту хороших модулів ffmpeg немає, тому ми застосуємо команду exec, що дозволяє нам виконувати термінальні команди зсередини Node.js.

exec(“ffmpeg -i Video/” + Name + ” -ss 01:30 -r 1 -an -vframes 1 -f mjpeg Video/” + Name + “.jpg”, function(err){
socket.emit(‘Done’, {‘Image’ : ‘Video/’ + Name + ‘.jpg’});
});

Команда ffmpeg згенерує одну піктограму на позначці 1:30 і збереже її в папку Video/ тека з файлом .jpg. Редагувати час піктограми можна, змінюючи параметр -ss. Як тільки піктограма сформовано, ми эмитируем подія Done. Тепер давайте повернемося до HTML-сторінці.

Крок 8: Закінчення

Подія Done видалить індикатор виконання і замінить його на зображення-мініатюру. Так як Node.js не встановлений в якості веб-сервера, вам потрібно для завантаження зображення помістити в змінну Path розташування свого сервера (наприклад, Apache).

var Path = “http://localhost/”;
socket.on(‘Done’, function (data){
var Content = “Video Successfully Uploaded !!”
Content += “Як створити відновляється відео-завантажувач в Node.js
“;
Content += “Upload Another”;
document.getElementById(‘UploadArea’).innerHTML = Content;
document.getElementById(‘Restart’).addEventListener. (‘click’, Refresh);
});
function Refresh(){
location.reload(true);
}

Вище ми додали кнопку початку закачування іншого файлу; все, що вона робить – оновлює сторінку.

Як створити відновляється відео-завантажувач в Node.js

Висновок

Ось і все, що стосується завантажувача, і, звичайно, можна уявити всі його можливості при об’єднанні з базою даних і HTML5-плеєром! Сподіваюся, вам сподобався цей підручник! Внизу в розділі коментарів дайте мені знати, що ви про нього думаєте.