Om du någonsin har laddat upp en betydligt stor videofil känner du den här känslan: du är 90% klar och uppdaterar sidan oavsiktligt - måste börja om igen.
I den här handledningen demonstrerar jag hur man gör en videouppladdare för din webbplats som kan återuppta en avbruten uppladdning och generera en miniatyrbild när den är klar.
För att göra den här uppladdaren återupptagen måste servern hålla reda på hur mycket en fil redan har laddats upp och kunna fortsätta från var den slutade. För att uppnå denna uppgift ger vi fullständig kontroll över Node.js-servern för att begära specifika datablock och HTML-formuläret hämtar dessa förfrågningar och skickar nödvändig information till servern.
För att hantera denna kommunikation använder vi Socket.io. Om du aldrig hört talas om Socket.io är det ett ramverk för realtidskommunikation mellan Node.js och en HTML-webbsida - gräva dig mer i det här inom kort.
Detta är det grundläggande begreppet; vi börjar med HTML-formuläret.
Jag ska hålla HTML ganska enkelt; allt vi behöver är en inmatning för att välja en fil, en textruta för namnet och en knapp för att starta uppladdningen. Här är den nödvändiga koden:
Video uppladdare
Observera att jag har förpackat innehållet i en spänn; Vi använder det senare för att uppdatera sidans layout med JavaScript. Jag kommer inte att täcka CSS i denna handledning, men du kan ladda ner källkoden, om du vill använda minen.
HTML5 är fortfarande relativt ny och stöds inte helt i alla webbläsare. Det första vi behöver göra innan vi går vidare är att användarens webbläsare stöder HTML5-fil API och FileReader-klassen.
Filen Classreader tillåter oss att öppna och läsa delar av en fil och skicka data som en binär sträng till servern. Här är JavaScript för funktionsdetektering:
window.addEventListener ("load", Ready); funktionen Ready () if (window.File && window.FileReader) // Det här är de relevanta HTML5-objekten som vi ska använda document.getElementById ('UploadButton'). addEventListener ('click', StartUpload); document.getElementById ('FileBox'). addEventListener ('Change', FileChosen); else document.getElementById ('UploadArea'). innerHTML = "Din webbläsare stöder inte filen API, uppdatera din webbläsare";
Koden ovan lägger dessutom till händelsehanterare till knappen och filinmatningen i formuläret. De FileChosen
funktionen ställer helt enkelt in en global variabel med filen - så att vi kan komma åt den senare - och fyller i namnfältet så att användaren har en referenspunkt när namnet filas. Här är FileChosen
fungera:
var SelectedFile; funktion FileChosen (evnt) SelectedFile = evnt.target.files [0]; document.getElementById ('NameBox') .värde = SelectedFile.name;
Innan vi skriver startup
funktion måste vi konfigurera Node.js-servern med socket.io; låt oss ta hand om det nu.
Som jag nämnde tidigare använder jag Socket.io för kommunikation mellan servern och HTML-filen. För att ladda ner Socket.io, skriv npm installera socket.io
in i ett terminalfönster (förutsatt att du har installerat Node.js), när du väl har navigerat till den här projektkatalogen. Hur socket.io fungerar är: antingen servern eller klienten "skickar ut" en händelse, och sedan tar den andra sidan upp den här händelsen i form av en funktion med möjlighet att skicka JSON-data fram och tillbaka. För att komma igång, skapa en tom JavaScript-fil och placera följande kod i den.
var app = kräver ('http'). createServer (handler), io = kräver ('socket.io'). lyssna (app), fs = kräva ('fs'), exec = kräva ('child_process'). , util = kräver ('util') app.listen (8080); funktionshanterare (req, res) fs.readFile (__ dirname + '/index.html', funktion (fel, data) om (err) res.writeHead (500); return res.end html '); res.writeHead (200); res.end (data);); io.sockets.on ("anslutning", funktion (uttag) // händelser kommer att gå här);
De första fem raderna innehåller de obligatoriska biblioteken, nästa rad instruerar servern att lyssna på port 8080 och handlarfunktionen skickar helt enkelt innehållet i vår HTML-fil till användaren när han kommer till webbplatsen.
De två sista raderna är socket.io-hanteraren och kommer att ringas när någon ansluter, via Socket.io.
Nu kan vi gå tillbaka till HTML-filen och definiera några socket.io-händelser.
För att börja använda Socket.io på vår sida behöver vi först länka till dess JavaScript-bibliotek. Du gör det på samma sätt som du skulle referera till ett bibliotek: hänvisa det till huvudområdet. Lägg till följande på sidan, före dina skript, självklart.
Oroa dig inte för att få den här filen, eftersom den genereras vid körning av Node.js-servern.
Nu kan vi skriva startup
funktion som vi anslutit till vår knapp:
var socket = io.connect ('http: // localhost: 8080'); var FReader; var namn funktion StartUpload () if (document.getElementById ('FileBox') .värde! = "") FReader = new FileReader (); Namn = document.getElementById ('NameBox'). Värde; var innehåll = "Uppladdning "+ SelectedFile.name +" som "+ Name +""; Innehåll + = '0%'; Innehåll + = " - 0/ "+ Math.round (SelectedFile.size / 1048576) +" MB"; document.getElementById ('UploadArea'). innerHTML = Innehåll; FReader.onload = funktion (evnt) socket.emit ('Ladda upp', 'Namn': Namn, Data: evnt.target.result); socket.emit ('Start', 'Name': Name, 'Size': SelectedFile.size); else alert ("Vänligen välj en fil");
Den första raden kopplas till Socket.io-servern; Nästa, vi har skapat två variabler för File Reader och namnet på filen, eftersom vi kommer att behöva global tillgång till dessa. Inuti funktionen försäkrade vi först att användaren valt en fil, och om de gjorde så skapade vi Filereader
, och uppdatera DOM med en bra framdriftsfält.
FileReaderens onload
Metoden heter varje gång den läser vissa data. allt vi behöver göra är att avge en Ladda upp
händelse och skicka data till servern. Slutligen avger vi en Start
händelse, passerar i filens namn och storlek till Node.js-servern.
Låt oss nu återvända till Node.js-filen och implementera hanterare för dessa två händelser.
Du måste rensa bufferten så ofta, eller servern kommer att krascha på grund av minnesöverbelastning.
Socket.io-händelserna går in i hanteraren som vi har på den sista raden i vår Node.js-fil. Den första händelsen som vi ska genomföra är den Start
händelse som utlöses när användaren klickar på Ladda upp knapp.
Jag nämnde tidigare att servern ska ha kontroll över vilka data den vill ta emot nästa; Det här gör det möjligt att fortsätta från en tidigare uppladdning som var ofullständig. Det gör det genom att först bestämma om det fanns en fil med det här namnet som inte slutförde uppladdning, och om så är fallet kommer det att fortsätta från var det släcktes. annars kommer det att börja i början. Vi skickar dessa data i halva megabyte steg, vilket kommer ut till 524288 byte.
För att hålla reda på olika uppladdningar som händer samtidigt måste vi lägga till en variabel för att lagra allt. Till början av filen lägger du till var filer = ; '
Här är koden för Start
händelse:
socket.on ('Start', funktion (data) // data innehåller de variabler som vi passerade genom HTML-filen var Name = data ['Name']; Filer [Namn] = // Skapa en ny post i Filerna Variable FileSize: data ['Size'], Data: "", Nedladdat: 0 var Place = 0; försök var Stat = fs.statSync ('Temp /' + Namn), om (Stat.isFile ) Files [Name] ['Downloaded'] = Stat.size; Plats = Stat.size / 524288; catch (er) // Det är en ny fil fs.open ("Temp /" + Name, " a, 0755, funktion (err, fd) om (err) console.log (err); annars Filer [Namn] ['Handler'] = fd; // Vi lagrar filhanteraren så att vi kan skriva till det senare socket.emit ('MoreData', 'Place': Plats, Procent: 0);;;);
Först lägger vi till den nya filen till filer
array, med storleken, data och mängd bitar som hittills hämtats. De Plats
variabla butiker där i filen vi är upp till - den är vanligtvis till 0, vilket är början. Vi kontrollerar då om filen redan existerar (dvs den var i mitten och stoppad), och uppdatera variablerna i enlighet därmed. Oavsett om det är en ny uppladdning eller inte, öppnar vi nu filen för att skriva till temp /
mapp och avge MoreData
händelse för att begära nästa avsnitt av data från HTML-filen.
Nu måste vi lägga till Ladda upp
händelse som, om du kommer ihåg, heter varje gång ett nytt datablock läses. Här är funktionen:
socket.on ('Upload', funktion (data) var Namn = data ['Namn']; Filer [Namn] ['Downloaded'] + = data ['Data']. Längd; Filer [Namn] ['Data '] + = data [' Data ']; om (Filer [Namn] [' Downloaded '] == Filer [Namn] [' Filstorlek ']) // Om filen är fullständigt uppladdad fs.write (Filer [Namn] ['Handler'], Filer [Namn] ['Data'], null, 'Binär', funktion (err, Writen) // Hämta miniatyr här); annars om (Filer [Namn] ['Data'] .length> 10485760) // Om databufferten når 10 MB fs.write (Filer [Namn] ['Handler'], Filer [Namn] ['Data'], null, 'Binär', funktion (fel, Writen) Filer [Namn] ['Data'] = ""; // Återställ Buffert var Plats = Filer [Namn] ['Downloaded'] / 524288; Var Procent = (Filer [Namn] ['Nedladdat'] / Filer [ Namn] ['FileSize']) * 100; socket.emit ('MoreData', 'Plats': Plats, 'Procent': Procent);); else var Place = Filer [Namn] ['Downloaded '] / 524288; var Procent = (Filer [Namn] [' Downloaded '] / Filer [Namn] [' FileSize ']) * 100; socket.emit (' MoreData ', ' Plats ': Plats,' Procent ' : Procent););
De två första raderna i den här koden uppdaterar bufferten med nya data och uppdaterar den totala bytes nedladdade variabeln. Vi måste lagra data i en buffert och spara den i steg så att den inte kraschar servern på grund av minnesöverbelastning. varje tio megabyte kommer vi att spara och rensa bufferten.
Den första om
uttalandet avgör om filen är fullständigt uppladdad, den andra kontrollerar om bufferten har nått 10 MB, och slutligen begär vi MoreData
, passerar i procent gjort och nästa block av data för att hämta.
Nu kan vi gå tillbaka till HTML-filen och implementera MoreData
händelse och uppdatera framstegen.
Jag skapade en funktion för att uppdatera framdriftsfältet och mängden MB som laddades upp på sidan. Utöver det, den Mer data
händelse läser det block av data som servern begärde och skickar den vidare till servern.
För att dela filen i block använder vi fil API Skiva
kommando. Eftersom File API fortfarande är under utveckling måste vi använda webkitSlice
och mozSlice
för webbkit respektive Mozilla webbläsare.
socket.on ('MoreData', funktion (data) UpdateBar (data ['Procent']); Var Plats = data ['Plats'] * 524288; // Nästa blockens startposition var NewFile; // Variabeln kommer att hålla det nya datablocket om (SelectedFile.webkitSlice) NewFile = SelectedFile.webkitSlice (Plats, Plats + Math.min (524288, (SelectedFile.size-Place)), annars NewFile = SelectedFile.mozSlice (Plats, Plats + Math.min (524288, (SelectedFile.size-Place))); FReader.readAsBinaryString (NewFile);); funktion UpdateBar (procent) document.getElementById ('ProgressBar'). style.width = procent + '%'; document.getElementById ('procent'). innerHTML = (Math.round (procent * 100) / 100) + '%'; var MBDone = Math.round (((procent / 100,0) * SelectedFile.size) / 1048576); document.getElementById ('MB'). innerHTML = MBDone;
Med den här sista funktionen är uppladdaren klar! Allt vi har kvar att göra är att flytta den färdiga filen ut ur temp /
mapp och generera miniatyren.
Innan vi skapar miniatyrbilden måste vi flytta filen ur den tillfälliga mappen. Vi kan göra detta genom att använda filströmmar och pump
metod. De pump
Metoden tar in en läs- och skrivström och buffrar data över. Du bör lägga till den här koden där jag skrev "Generera miniatyrbild här" i Ladda upp
händelse:
var inp = fs.createReadStream ("Temp /" + Namn); var ut = fs.createWriteStream ("Video /" + Namn); util.pump (inp, ut, funktion () fs.unlink ("Temp /" + Namn, funktion () // Detta raderar den temporära filen // Moving File Completed););
Vi har lagt till kommandot förlänk Detta tar bort den temporära filen, efter att vi har avslutat kopiering av den. Nu på miniatyrbilden: vi använder ffmpeg för att skapa miniatyrer, eftersom det kan hantera flera format, och det är en cinch att installera. Vid skrivetiden finns det inga bra ffmpeg-moduler, så vi använder exec
kommando, som tillåter oss att utföra Terminalkommandon från Node.js.
exec ("ffmpeg -i Video /" + Name + "-ss 01:30 -r 1 -an -vframes 1 -f mjpeg Video /" + Namn + ".jpg", funktion (err) socket.emit (' Done ', ' Image ':' Video / '+ Namn +' .jpg '););
Detta ffmpeg-kommando kommer att generera en miniatyrbild på 1:30-märket och spara det till Video/
mapp med a .jpg
filtyp. Du kan redigera tiden för miniatyren genom att ändra -ss
parameter. När miniatyrbilden har genererats avger vi Gjort
händelse. Nu, låt oss gå tillbaka till HTML-sidan och implementera den.
De Gjort
händelsen tar bort framdriftsfältet och ersätter det med miniatyrbilden. Eftersom Node.js inte är inställd som en webbserver måste du placera din server (t.ex. Apache) i Väg
variabel, för att ladda bilden.
var sökväg = "http: // localhost /"; socket.on ('Done', funktion (data) var Content = "Video uppladdat framgångsrikt !!" Innehåll + = "
"; Innehåll + ="