Создаем видео загрузчик при помощи Node.js

в 5:56, , рубрики: html5, javascript, веб-дизайн, Веб-разработка, загрузчик

image

Если вы когда-либо загружали видеофайл на сайт, то знаете это чувство когда загрузилось 90% и вы случайно обновляете страницу.

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

Исходные файлы
ДЕМО

Введение

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

Чтобы обработать эту передачу информации, мы будем использовать Socket.io. Если никогда не слышали о Socket.io, это — платформа для связи в режиме реального времени между Node.js, и 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">
         <label for="NameBox">Name: </label><input type="text" id="NameBox">
 
         <button  type='button' id='UploadButton' class='Button'>Upload</button>
      </span>
   </div>
</body>

Я добавил содержание страницы в wrapper; мы будем использовать его позже, чтобы обновить расположение страницы в JavaScript. Я не буду показывать, как настраивать CSS в этом уроке, но вы можете загрузить свой исходный код.

image

Шаг – 2. Кроссбраузерность

HTML5 все еще относительно молод, и еще не полностью поддерживается во всех браузерах. Первая вещь, которую мы должны сделать, удостоверится, что браузер пользователя поддерживает API Файлы HTML5 и класс FileReader.

Класс FileReader позволяет нам открывать и читать части файла и передавать данные серверу в двоичной системе.

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 socket.io. Работа 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

<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; затем, мы создали две переменные для чтения и имени файла. В функции мы сначала удостоверились, что пользователь выбрал файл, затем создаем FileReader и обновляем DOM с индикатором загрузки.

Метод FileReader вызывают каждый раз, когда он считывает некоторые данные; все, что мы должны сделать, запустить событие Upload и отправить данные на сервер. Наконец, мы запускаем событие Start, которое передает имя файла и его размер на сервер.

Теперь, давайте возвратимся к файлу Node.js и реализуем обработчики для этих двух событий.

Шаг – 5. Управление

События socket.io находятся в обработчике, который мы добавили на последней строке нашего файла Node.js. Первое событие, которое мы реализуем, является событием Start, которое запускаеться когда пользователь нажимает кнопку Upload.

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

Чтобы отслеживать несколько загрузок, происходящих одновременно, мы должны добавить переменную, чтобы сохранить их все.

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. Сначала проверяем, является ли это новой закачкой или нет, затем открываем файл для того, чтобы записать его в папку временных файлов, и запускаем событие 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});
      }
   });

Первые две строки этого кода обновляют буфер с новыми данными и обновляют загруженную переменную. Мы должны хранить данные в буфере, так, чтобы они не разрушали сервер из-за перегрузки памяти; мы будем очищать буфер после каждых 10 мегабайтов.

Первое что оператор будет определять, загружен ли файл полностью, вторая проверка это, достиг ли буфер размера 10 Мбайт, и, наконец, мы запрашиваем MoreData.

Шаг – 6. Отслеживание загрузки

Я создал функцию, чтобы обновить индикатор загрузки и размер файла на странице. В дополнение к этому, событие More Data читает блок требуемых данных и передает их на сервер.

Чтобы разбить файл на блоки, мы используем команду Slice API. Так как API находится все еще в разработке, мы должны использовать webkitSlice и mozSlice для браузеров Webkit и Mozilla.

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;
}

Нам осталось сделать, что бы наш загружаемый файл перемещался из папки Временных файлов и генерировал обложку.

image

Шаг – 7. Обложка

Прежде, чем мы сгенерируем обложку, нужно переместить файл из временной папки. Мы можем сделать это при помощи файловых потоков и метода pump.

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 команду, которая позволяет нам выполнять команды Terminal из 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 не установлен как веб-сервер, мы должны определить расположение сервера (например, Apache) в переменной Path, чтобы загрузить изображение.

var Path = "http://localhost/";
 
socket.on('Done', function (data){
   var Content = "Video Successfully Uploaded !!"
   Content += "<img id='Thumb' src='" + Path + data['Image'] + "' alt='" + Name + "'>
";
   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);
}

Также добавили кнопку загрузки другого файла.

image

Исходные файлы
ДЕМО

Автор: Lecaw

  1. Дмитрий:

    загрузите, пожалуйста, исходные файлы. ссылка не работает

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js