Как создать возобновляющийся видео-загрузчик в Node.js

Как создать возобновляющийся видео-загрузчик в Node.js

От автора: если вы когда-либо выкладывали сравнительно большой видео-файл, то испытывали это ощущение: закончили уже на 90% и нечаянно обновили страницу – и приходится начинать все снова.

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

скачать исходники

Вступление

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

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

Современные тенденции и подходы в веб-разработке

Узнайте алгоритм быстрого профессионального роста с нуля в сайтостроении

Узнать подробнее

Шаг 1: HTML

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

<body>
 <div id="UploadBox">
 <h2>Video Uploader</h2>
 <span id='UploadArea'>
 <label for="FileBox">Choose A File: </label><input type="file" id="FileBox"><br>
 <label for="NameBox">Name: </label><input type="text" id="NameBox"><br>
 
 <button  type='button' id='UploadButton' class='Button'>Upload</button>
 </span>
 </div>
</body>

Обратите внимание, что я обернул содержимое в span; этим мы воспользуемся позже для обновления разметки страницы с помощью JavaScript. В этом учебнике я не собираюсь разжевывать весь CSS, а вы, если захотите воспользоваться моим исходным кодом, можете его скачать.

Шаг 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. Добавьте на страницу следующее, естественно, перед своими скриптами.

<script src="/socket.io/socket.io.js"></script>

Не беспокойтесь по поводу получения этого файла, так как он генерируется сервером 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 = "<span id='NameArea'>Uploading " + SelectedFile.name + " as " + Name + "</span>";
 Content += '<div id="ProgressContainer"><div id="ProgressBar"></div></div><span id="percent">0%</span>';
 Content += "<span id='Uploaded'> - <span id='MB'>0</span>/" + Math.round(SelectedFile.size / 1048576) + "MB</span>";
 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/ и сгенерировать пиктограмму.

Шаг 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 += "<img id='Thumb' src='" + Path + data['Image'] + "' alt='" + Name + "'><br>";
 Content += "<button  type='button' name='Upload' value='' id='Restart' class='Button'>Upload Another</button>";
 document.getElementById('UploadArea').innerHTML = Content;
 document.getElementById('Restart').addEventListener('click', Refresh);
});
function Refresh(){
 location.reload(true);
}

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

Заключение

Вот и все, что касается загрузчика, и, конечно, можно представить все его возможности при объединении с базой данных и HTML5-плеером! Надеюсь, вам понравился этот учебник! Внизу в разделе комментариев дайте мне знать, что вы о нем думаете.

Автор: Gabriel Manricks

Источник: http://net.tutsplus.com/

Редакция: Команда webformyself.

Современные тенденции и подходы в веб-разработке

Узнайте алгоритм быстрого профессионального роста с нуля в сайтостроении

Узнать подробнее
Самые свежие новости IT и веб-разработки на нашем Telegram-канале

JavaScript&jQuery с нуля до профи

Пройдите пошаговый видеокурс по JavaScript&jQuery

Научиться

Метки: ,

Похожие статьи:

Комментарии Вконтакте:

Комментарии Facebook:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree