Added basic upload support
This commit is contained in:
parent
83351c4b5b
commit
976a6b4a8c
8 changed files with 532 additions and 72 deletions
97
app/app.go
97
app/app.go
|
@ -2,11 +2,15 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
rice "github.com/GeertJohan/go.rice"
|
rice "github.com/GeertJohan/go.rice"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
|
@ -21,7 +25,7 @@ type App struct {
|
||||||
Config *Config
|
Config *Config
|
||||||
Library *media.Library
|
Library *media.Library
|
||||||
Watcher *fsnotify.Watcher
|
Watcher *fsnotify.Watcher
|
||||||
Templates *template.Template
|
Templates *templateStore
|
||||||
Feed []byte
|
Feed []byte
|
||||||
Listener net.Listener
|
Listener net.Listener
|
||||||
Router *mux.Router
|
Router *mux.Router
|
||||||
|
@ -49,14 +53,26 @@ func NewApp(cfg *Config) (*App, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
a.Listener = ln
|
a.Listener = ln
|
||||||
// Setup Templates
|
|
||||||
|
// Templates
|
||||||
box := rice.MustFindBox("../templates")
|
box := rice.MustFindBox("../templates")
|
||||||
index := template.New("index")
|
|
||||||
a.Templates = template.Must(index.Parse(box.MustString("index.html")))
|
a.Templates = newTemplateStore("base")
|
||||||
|
|
||||||
|
indexTemplate := template.New("index")
|
||||||
|
template.Must(indexTemplate.Parse(box.MustString("index.html")))
|
||||||
|
template.Must(indexTemplate.Parse(box.MustString("base.html")))
|
||||||
|
a.Templates.Add("index", indexTemplate)
|
||||||
|
|
||||||
|
uploadTemplate := template.New("upload")
|
||||||
|
template.Must(uploadTemplate.Parse(box.MustString("upload.html")))
|
||||||
|
template.Must(uploadTemplate.Parse(box.MustString("base.html")))
|
||||||
|
a.Templates.Add("upload", uploadTemplate)
|
||||||
|
|
||||||
// Setup Router
|
// Setup Router
|
||||||
r := mux.NewRouter().StrictSlash(true)
|
r := mux.NewRouter().StrictSlash(true)
|
||||||
r.HandleFunc("/", a.indexHandler).Methods("GET")
|
r.HandleFunc("/", a.indexHandler).Methods("GET")
|
||||||
|
r.HandleFunc("/upload", a.uploadHandler).Methods("GET", "POST")
|
||||||
r.HandleFunc("/v/{id}.mp4", a.videoHandler).Methods("GET")
|
r.HandleFunc("/v/{id}.mp4", a.videoHandler).Methods("GET")
|
||||||
r.HandleFunc("/v/{prefix}/{id}.mp4", a.videoHandler).Methods("GET")
|
r.HandleFunc("/v/{prefix}/{id}.mp4", a.videoHandler).Methods("GET")
|
||||||
r.HandleFunc("/t/{id}", a.thumbHandler).Methods("GET")
|
r.HandleFunc("/t/{id}", a.thumbHandler).Methods("GET")
|
||||||
|
@ -96,6 +112,18 @@ func (a *App) Run() error {
|
||||||
return http.Serve(a.Listener, a.Router)
|
return http.Serve(a.Listener, a.Router)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) render(name string, w http.ResponseWriter, ctx interface{}) {
|
||||||
|
buf, err := a.Templates.Exec(name, ctx)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HTTP handler for /
|
// HTTP handler for /
|
||||||
func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
|
func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("/")
|
log.Printf("/")
|
||||||
|
@ -103,13 +131,60 @@ func (a *App) indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if len(pl) > 0 {
|
if len(pl) > 0 {
|
||||||
http.Redirect(w, r, "/v/"+pl[0].ID, 302)
|
http.Redirect(w, r, "/v/"+pl[0].ID, 302)
|
||||||
} else {
|
} else {
|
||||||
a.Templates.ExecuteTemplate(w, "index", &struct {
|
ctx := &struct {
|
||||||
Playing *media.Video
|
Playing *media.Video
|
||||||
Playlist media.Playlist
|
Playlist media.Playlist
|
||||||
}{
|
}{
|
||||||
Playing: &media.Video{ID: ""},
|
Playing: &media.Video{ID: ""},
|
||||||
Playlist: a.Library.Playlist(),
|
Playlist: a.Library.Playlist(),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
a.render("index", w, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP handler for /upload
|
||||||
|
func (a *App) uploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == "GET" {
|
||||||
|
log.Printf("GET /upload")
|
||||||
|
ctx := &struct{}{}
|
||||||
|
a.render("upload", w, ctx)
|
||||||
|
} else if r.Method == "POST" {
|
||||||
|
// TODO: Move to a constant
|
||||||
|
r.ParseMultipartForm((10 << 20) * 10) // 100MB
|
||||||
|
|
||||||
|
file, handler, err := r.FormFile("video_file")
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("error processing form: %w", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// TODO: Allow the user to pick this and don't hard code it.
|
||||||
|
collection := "videos"
|
||||||
|
fn := filepath.Join(collection, handler.Filename)
|
||||||
|
|
||||||
|
f, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0755)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("error opening file for writing: %w", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(f, file)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("error writing file: %w", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Library.Add(fn)
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "Successfully uploaded video: %s", handler.Filename)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,23 +199,25 @@ func (a *App) pageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("/v/%s", id)
|
log.Printf("/v/%s", id)
|
||||||
playing, ok := a.Library.Videos[id]
|
playing, ok := a.Library.Videos[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
a.Templates.ExecuteTemplate(w, "index", &struct {
|
ctx := &struct {
|
||||||
Playing *media.Video
|
Playing *media.Video
|
||||||
Playlist media.Playlist
|
Playlist media.Playlist
|
||||||
}{
|
}{
|
||||||
Playing: &media.Video{ID: ""},
|
Playing: &media.Video{ID: ""},
|
||||||
Playlist: a.Library.Playlist(),
|
Playlist: a.Library.Playlist(),
|
||||||
})
|
}
|
||||||
|
a.render("upload", w, ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
a.Templates.ExecuteTemplate(w, "index", &struct {
|
ctx := &struct {
|
||||||
Playing *media.Video
|
Playing *media.Video
|
||||||
Playlist media.Playlist
|
Playlist media.Playlist
|
||||||
}{
|
}{
|
||||||
Playing: playing,
|
Playing: playing,
|
||||||
Playlist: a.Library.Playlist(),
|
Playlist: a.Library.Playlist(),
|
||||||
})
|
}
|
||||||
|
a.render("index", w, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP handler for /v/id.mp4
|
// HTTP handler for /v/id.mp4
|
||||||
|
|
File diff suppressed because one or more lines are too long
53
app/templates.go
Normal file
53
app/templates.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type templateMap map[string]*template.Template
|
||||||
|
|
||||||
|
type templateStore struct {
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
base string
|
||||||
|
templates templateMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTemplateStore(base string) *templateStore {
|
||||||
|
return &templateStore{
|
||||||
|
base: base,
|
||||||
|
templates: make(templateMap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *templateStore) Add(name string, template *template.Template) {
|
||||||
|
t.Lock()
|
||||||
|
defer t.Unlock()
|
||||||
|
|
||||||
|
t.templates[name] = template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *templateStore) Exec(name string, ctx interface{}) (io.WriterTo, error) {
|
||||||
|
t.Lock()
|
||||||
|
defer t.Unlock()
|
||||||
|
|
||||||
|
template, ok := t.templates[name]
|
||||||
|
if !ok {
|
||||||
|
log.Printf("template %s not found", name)
|
||||||
|
return nil, fmt.Errorf("no such template: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
err := template.ExecuteTemplate(buf, t.base, ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error parsing template %s: %s", name, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
109
static/theme.css
109
static/theme.css
|
@ -193,3 +193,112 @@ main {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Upload */
|
||||||
|
.upload_form_cont {
|
||||||
|
background: -moz-linear-gradient(#ffffff, #f2f2f2);
|
||||||
|
background: -ms-linear-gradient(#ffffff, #f2f2f2);
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f2f2f2));
|
||||||
|
background: -webkit-linear-gradient(#ffffff, #f2f2f2);
|
||||||
|
background: -o-linear-gradient(#ffffff, #f2f2f2);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2');
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2')";
|
||||||
|
background: linear-gradient(#ffffff, #f2f2f2);
|
||||||
|
color:#000;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
#upload_form {
|
||||||
|
float:left;
|
||||||
|
padding:20px;
|
||||||
|
width:700px;
|
||||||
|
}
|
||||||
|
#upload_form > div {
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
#speed,#remaining {
|
||||||
|
float:left;
|
||||||
|
width:100px;
|
||||||
|
}
|
||||||
|
#b_transfered {
|
||||||
|
float:right;
|
||||||
|
text-align:right;
|
||||||
|
}
|
||||||
|
.clear_both {
|
||||||
|
clear:both;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
border-radius:10px;
|
||||||
|
-moz-border-radius:10px;
|
||||||
|
-ms-border-radius:10px;
|
||||||
|
-o-border-radius:10px;
|
||||||
|
-webkit-border-radius:10px;
|
||||||
|
border:1px solid #ccc;
|
||||||
|
font-size:14pt;
|
||||||
|
padding:5px 10px;
|
||||||
|
}
|
||||||
|
input[type=button] {
|
||||||
|
background: -moz-linear-gradient(#ffffff, #dfdfdf);
|
||||||
|
background: -ms-linear-gradient(#ffffff, #dfdfdf);
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #dfdfdf));
|
||||||
|
background: -webkit-linear-gradient(#ffffff, #dfdfdf);
|
||||||
|
background: -o-linear-gradient(#ffffff, #dfdfdf);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dfdfdf');
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dfdfdf')";
|
||||||
|
background: linear-gradient(#ffffff, #dfdfdf);
|
||||||
|
}
|
||||||
|
#video_file {
|
||||||
|
width:400px;
|
||||||
|
}
|
||||||
|
#progress_info {
|
||||||
|
font-size:10pt;
|
||||||
|
}
|
||||||
|
#fileinfo,#error,#error2,#abort,#warnsize {
|
||||||
|
color:#aaa;
|
||||||
|
display:none;
|
||||||
|
font-size:10pt;
|
||||||
|
font-style:italic;
|
||||||
|
margin-top:10px;
|
||||||
|
}
|
||||||
|
#progress {
|
||||||
|
border:1px solid #ccc;
|
||||||
|
display:none;
|
||||||
|
float:left;
|
||||||
|
height:14px;
|
||||||
|
border-radius:10px;
|
||||||
|
-moz-border-radius:10px;
|
||||||
|
-ms-border-radius:10px;
|
||||||
|
-o-border-radius:10px;
|
||||||
|
-webkit-border-radius:10px;
|
||||||
|
background: -moz-linear-gradient(#66cc00, #4b9500);
|
||||||
|
background: -ms-linear-gradient(#66cc00, #4b9500);
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #66cc00), color-stop(100%, #4b9500));
|
||||||
|
background: -webkit-linear-gradient(#66cc00, #4b9500);
|
||||||
|
background: -o-linear-gradient(#66cc00, #4b9500);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#66cc00', endColorstr='#4b9500');
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#66cc00', endColorstr='#4b9500')";
|
||||||
|
background: linear-gradient(#66cc00, #4b9500);
|
||||||
|
}
|
||||||
|
#progress_percent {
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
#upload_response {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius:10px;
|
||||||
|
-moz-border-radius:10px;
|
||||||
|
-ms-border-radius:10px;
|
||||||
|
-o-border-radius:10px;
|
||||||
|
-webkit-border-radius:10px;
|
||||||
|
box-shadow: 0 0 5px #ccc;
|
||||||
|
background: -moz-linear-gradient(#bbb, #eee);
|
||||||
|
background: -ms-linear-gradient(#bbb, #eee);
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #bbb), color-stop(100%, #eee));
|
||||||
|
background: -webkit-linear-gradient(#bbb, #eee);
|
||||||
|
background: -o-linear-gradient(#bbb, #eee);
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#bbb', endColorstr='#eee');
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#bbb', endColorstr='#eee')";
|
||||||
|
background: linear-gradient(#000000, #1e1e1e);
|
||||||
|
}
|
||||||
|
|
145
static/upload.js
Normal file
145
static/upload.js
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
// common variables
|
||||||
|
var iBytesUploaded = 0;
|
||||||
|
var iBytesTotal = 0;
|
||||||
|
var iPreviousBytesLoaded = 0;
|
||||||
|
var iMaxFilesize = 104857600; // 100MB
|
||||||
|
var oTimer = 0;
|
||||||
|
var sResultFileSize = '';
|
||||||
|
function secondsToTime(secs) { // we will use this function to convert seconds in normal time format
|
||||||
|
var hr = Math.floor(secs / 3600);
|
||||||
|
var min = Math.floor((secs - (hr * 3600))/60);
|
||||||
|
var sec = Math.floor(secs - (hr * 3600) - (min * 60));
|
||||||
|
if (hr < 10) {hr = "0" + hr; }
|
||||||
|
if (min < 10) {min = "0" + min;}
|
||||||
|
if (sec < 10) {sec = "0" + sec;}
|
||||||
|
if (hr) {hr = "00";}
|
||||||
|
return hr + ':' + min + ':' + sec;
|
||||||
|
};
|
||||||
|
function bytesToSize(bytes) {
|
||||||
|
var sizes = ['Bytes', 'KB', 'MB'];
|
||||||
|
if (bytes == 0) return 'n/a';
|
||||||
|
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
||||||
|
return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
function fileSelected() {
|
||||||
|
// hide different warnings
|
||||||
|
document.getElementById('upload_response').style.display = 'none';
|
||||||
|
document.getElementById('error').style.display = 'none';
|
||||||
|
document.getElementById('error2').style.display = 'none';
|
||||||
|
document.getElementById('abort').style.display = 'none';
|
||||||
|
document.getElementById('warnsize').style.display = 'none';
|
||||||
|
// get selected file element
|
||||||
|
var oFile = document.getElementById('image_file').files[0];
|
||||||
|
// filter for image files
|
||||||
|
var rFilter = /^(image\/bmp|image\/gif|image\/jpeg|image\/png|image\/tiff)$/i;
|
||||||
|
if (! rFilter.test(oFile.type)) {
|
||||||
|
document.getElementById('error').style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// little test for filesize
|
||||||
|
if (oFile.size > iMaxFilesize) {
|
||||||
|
document.getElementById('warnsize').style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// get preview element
|
||||||
|
var oImage = document.getElementById('preview');
|
||||||
|
// prepare HTML5 FileReader
|
||||||
|
var oReader = new FileReader();
|
||||||
|
oReader.onload = function(e){
|
||||||
|
// e.target.result contains the DataURL which we will use as a source of the image
|
||||||
|
oImage.src = e.target.result;
|
||||||
|
oImage.onload = function () { // binding onload event
|
||||||
|
// we are going to display some custom image information here
|
||||||
|
sResultFileSize = bytesToSize(oFile.size);
|
||||||
|
document.getElementById('fileinfo').style.display = 'block';
|
||||||
|
document.getElementById('filename').innerHTML = 'Name: ' + oFile.name;
|
||||||
|
document.getElementById('filesize').innerHTML = 'Size: ' + sResultFileSize;
|
||||||
|
document.getElementById('filetype').innerHTML = 'Type: ' + oFile.type;
|
||||||
|
document.getElementById('filedim').innerHTML = 'Dimension: ' + oImage.naturalWidth + ' x ' + oImage.naturalHeight;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// read selected file as DataURL
|
||||||
|
oReader.readAsDataURL(oFile);
|
||||||
|
}
|
||||||
|
function startUploading() {
|
||||||
|
// cleanup all temp states
|
||||||
|
iPreviousBytesLoaded = 0;
|
||||||
|
document.getElementById('upload_response').style.display = 'none';
|
||||||
|
document.getElementById('error').style.display = 'none';
|
||||||
|
document.getElementById('error2').style.display = 'none';
|
||||||
|
document.getElementById('abort').style.display = 'none';
|
||||||
|
document.getElementById('warnsize').style.display = 'none';
|
||||||
|
document.getElementById('progress_percent').innerHTML = '';
|
||||||
|
var oProgress = document.getElementById('progress');
|
||||||
|
oProgress.style.display = 'block';
|
||||||
|
oProgress.style.width = '0px';
|
||||||
|
// get form data for POSTing
|
||||||
|
//var vFD = document.getElementById('upload_form').getFormData(); // for FF3
|
||||||
|
var vFD = new FormData(document.getElementById('upload_form'));
|
||||||
|
// create XMLHttpRequest object, adding few event listeners, and POSTing our data
|
||||||
|
var oXHR = new XMLHttpRequest();
|
||||||
|
oXHR.upload.addEventListener('progress', uploadProgress, false);
|
||||||
|
oXHR.addEventListener('load', uploadFinish, false);
|
||||||
|
oXHR.addEventListener('error', uploadError, false);
|
||||||
|
oXHR.addEventListener('abort', uploadAbort, false);
|
||||||
|
oXHR.open('POST', '/upload');
|
||||||
|
oXHR.send(vFD);
|
||||||
|
// set inner timer
|
||||||
|
oTimer = setInterval(doInnerUpdates, 300);
|
||||||
|
}
|
||||||
|
function doInnerUpdates() { // we will use this function to display upload speed
|
||||||
|
var iCB = iBytesUploaded;
|
||||||
|
var iDiff = iCB - iPreviousBytesLoaded;
|
||||||
|
// if nothing new loaded - exit
|
||||||
|
if (iDiff == 0)
|
||||||
|
return;
|
||||||
|
iPreviousBytesLoaded = iCB;
|
||||||
|
iDiff = iDiff * 2;
|
||||||
|
var iBytesRem = iBytesTotal - iPreviousBytesLoaded;
|
||||||
|
var secondsRemaining = iBytesRem / iDiff;
|
||||||
|
// update speed info
|
||||||
|
var iSpeed = iDiff.toString() + 'B/s';
|
||||||
|
if (iDiff > 1024 * 1024) {
|
||||||
|
iSpeed = (Math.round(iDiff * 100/(1024*1024))/100).toString() + 'MB/s';
|
||||||
|
} else if (iDiff > 1024) {
|
||||||
|
iSpeed = (Math.round(iDiff * 100/1024)/100).toString() + 'KB/s';
|
||||||
|
}
|
||||||
|
document.getElementById('speed').innerHTML = iSpeed;
|
||||||
|
document.getElementById('remaining').innerHTML = '| ' + secondsToTime(secondsRemaining);
|
||||||
|
}
|
||||||
|
function uploadProgress(e) { // upload process in progress
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
iBytesUploaded = e.loaded;
|
||||||
|
iBytesTotal = e.total;
|
||||||
|
var iPercentComplete = Math.round(e.loaded * 100 / e.total);
|
||||||
|
var iBytesTransfered = bytesToSize(iBytesUploaded);
|
||||||
|
document.getElementById('progress_percent').innerHTML = iPercentComplete.toString() + '%';
|
||||||
|
document.getElementById('progress').style.width = (iPercentComplete * 4).toString() + 'px';
|
||||||
|
document.getElementById('b_transfered').innerHTML = iBytesTransfered;
|
||||||
|
if (iPercentComplete == 100) {
|
||||||
|
var oUploadResponse = document.getElementById('upload_response');
|
||||||
|
oUploadResponse.innerHTML = '<h1>Please wait...processing</h1>';
|
||||||
|
oUploadResponse.style.display = 'block';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById('progress').innerHTML = 'unable to compute';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function uploadFinish(e) { // upload successfully finished
|
||||||
|
var oUploadResponse = document.getElementById('upload_response');
|
||||||
|
oUploadResponse.innerHTML = e.target.responseText;
|
||||||
|
oUploadResponse.style.display = 'block';
|
||||||
|
document.getElementById('progress_percent').innerHTML = '100%';
|
||||||
|
document.getElementById('progress').style.width = '400px';
|
||||||
|
document.getElementById('filesize').innerHTML = sResultFileSize;
|
||||||
|
document.getElementById('remaining').innerHTML = '| 00:00:00';
|
||||||
|
clearInterval(oTimer);
|
||||||
|
}
|
||||||
|
function uploadError(e) { // upload error
|
||||||
|
document.getElementById('error2').style.display = 'block';
|
||||||
|
clearInterval(oTimer);
|
||||||
|
}
|
||||||
|
function uploadAbort(e) { // upload abort
|
||||||
|
document.getElementById('abort').style.display = 'block';
|
||||||
|
clearInterval(oTimer);
|
||||||
|
}
|
28
templates/base.html
Normal file
28
templates/base.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{{define "base"}}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/theme.css">
|
||||||
|
|
||||||
|
{{ template "stylesheets" . }}
|
||||||
|
{{ template "css" . }}
|
||||||
|
<title>Tube</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav>
|
||||||
|
<a href="/">Tube</a>
|
||||||
|
<a href="/upload">Upload</a>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
{{template "content" .}}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
{{ template "scripts" . }}
|
||||||
|
</html>
|
||||||
|
{{end}}
|
||||||
|
{{ define "css" }}{{ end }}
|
||||||
|
{{ define "scripts" }}{{ end }}
|
||||||
|
{{ define "stylesheets" }}{{ end }}
|
|
@ -1,16 +1,6 @@
|
||||||
|
{{ define "content" }}
|
||||||
{{ $playing := .Playing }}
|
{{ $playing := .Playing }}
|
||||||
<html>
|
<div id="player">
|
||||||
<head>
|
|
||||||
<title>Tube</title>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico">
|
|
||||||
<link rel="stylesheet" type="text/css" href="/static/theme.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<nav><a href="/">Tube</a></nav>
|
|
||||||
<main>
|
|
||||||
<div id="player">
|
|
||||||
{{ if $playing.ID }}
|
{{ if $playing.ID }}
|
||||||
<video id="video" controls poster="/t/{{ $playing.ID}}" src="/v/{{ $playing.ID }}.mp4"></video>
|
<video id="video" controls poster="/t/{{ $playing.ID}}" src="/v/{{ $playing.ID }}.mp4"></video>
|
||||||
<h1>{{ $playing.Title }}</h1>
|
<h1>{{ $playing.Title }}</h1>
|
||||||
|
@ -19,8 +9,8 @@
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<video id="video" controls></video>
|
<video id="video" controls></video>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div id="playlist">
|
<div id="playlist">
|
||||||
{{ range $m := .Playlist }}
|
{{ range $m := .Playlist }}
|
||||||
{{ if eq $m.ID $playing.ID }}
|
{{ if eq $m.ID $playing.ID }}
|
||||||
<a href="/v/{{ $m.ID }}" class="playing">
|
<a href="/v/{{ $m.ID }}" class="playing">
|
||||||
|
@ -34,7 +24,5 @@
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
{{end}}
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
36
templates/upload.html
Normal file
36
templates/upload.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{{define "content"}}
|
||||||
|
<div id="upload">
|
||||||
|
<form id="upload_form" enctype="multipart/form-data" method="POST" action="/upload">
|
||||||
|
<div>
|
||||||
|
<div><label for="video_file">Please select video file</label></div>
|
||||||
|
<div><input type="file" name="video_file" id="video_file" onchange="fileSelected();" /></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input type="button" value="Upload" onclick="startUploading()" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="error">You should select valid image files only!</div>
|
||||||
|
<div id="error2">An error occurred while uploading the file</div>
|
||||||
|
<div id="abort">The upload has been canceled by the user or the browser dropped the connection</div>
|
||||||
|
<div id="warnsize">Your file is very big. We can't accept it. Please select more small file</div>
|
||||||
|
|
||||||
|
<div id="progress_info">
|
||||||
|
<div id="progress"></div>
|
||||||
|
<div id="progress_percent"> </div>
|
||||||
|
<div class="clear_both"></div>
|
||||||
|
<div>
|
||||||
|
<div id="speed"> </div>
|
||||||
|
<div id="remaining"> </div>
|
||||||
|
<div id="b_transfered"> </div>
|
||||||
|
<div class="clear_both"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="upload_response"></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{define "scripts"}}
|
||||||
|
<script type="application/javascript" src="/static/upload.js"></script>
|
||||||
|
{{end}}
|
Loading…
Reference in a new issue