init
Signed-off-by: Mirror <voice@magicalmirro.red>
This commit is contained in:
commit
45d48b78ea
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
videopage
|
||||
web
|
||||
config.yml
|
||||
|
206
admin.go
Normal file
206
admin.go
Normal file
@ -0,0 +1,206 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AdminApi struct {
|
||||
mux *http.ServeMux
|
||||
}
|
||||
|
||||
func NewAdminApi() (a *AdminApi) {
|
||||
a = &AdminApi{}
|
||||
a.mux = http.NewServeMux()
|
||||
a.mux.HandleFunc("/", a.adminHandler)
|
||||
a.mux.HandleFunc("/delete", a.deleteVideo)
|
||||
a.mux.HandleFunc("/upload", a.uploadVideo)
|
||||
a.mux.HandleFunc("/rename", a.renameVideo)
|
||||
return a
|
||||
}
|
||||
func (a *AdminApi) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if a.checkAuth(w, r) {
|
||||
a.mux.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
func (a *AdminApi) checkAuth(w http.ResponseWriter, r *http.Request) (success bool) {
|
||||
success = false
|
||||
user, password, ok := r.BasicAuth()
|
||||
if subtle.ConstantTimeCompare([]byte(user), []byte(config.AdminUser)) == 1 &&
|
||||
subtle.ConstantTimeCompare([]byte(password), []byte(config.AdminPassword)) == 1 {
|
||||
success = true
|
||||
}
|
||||
if !success {
|
||||
if !ok {
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s\"", config.ApplicationName))
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
func (a *AdminApi) adminHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var v struct {
|
||||
ApplicationName string
|
||||
Videos []Video
|
||||
}
|
||||
v.ApplicationName = config.ApplicationName
|
||||
|
||||
dataDir := path.Join(config.DataDirectory, "video")
|
||||
dir, err := os.ReadDir(dataDir)
|
||||
if err != nil {
|
||||
log.Printf("admin: can't read data directory: %s", err.Error())
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for _, entry := range dir {
|
||||
if entry.Type().IsRegular() && strings.HasSuffix(entry.Name(), ".json") && entry.Name() != "index.json" {
|
||||
video, err := readVideoInfo(path.Join(dataDir, entry.Name()))
|
||||
if err != nil {
|
||||
log.Printf("admin: can't read video info: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
v.Videos = append(v.Videos, video)
|
||||
}
|
||||
}
|
||||
|
||||
err = executeTemplate(w, "admin.html", &v)
|
||||
if err != nil {
|
||||
log.Printf("Can't execute template \"admin.html\": %s", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AdminApi) deleteVideo(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
err := r.ParseForm()
|
||||
referer := r.Header.Get("Referer")
|
||||
if err != nil {
|
||||
log.Printf("can't parse form: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ID := r.Form.Get("fileid")
|
||||
ID = path.Clean(path.Base(ID))
|
||||
rawInfo, err := ioutil.ReadFile(path.Join(config.DataDirectory, "video", ID+".json"))
|
||||
if err != nil {
|
||||
log.Printf("can't read info: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var info Video
|
||||
err = json.Unmarshal(rawInfo, &info)
|
||||
if err != nil {
|
||||
log.Printf("can't read info: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = os.Remove(path.Join(config.DataDirectory, "video", info.Name))
|
||||
if err != nil {
|
||||
log.Printf("can't remove video: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = os.Remove(path.Join(config.DataDirectory, "video", ID+".json"))
|
||||
if err != nil {
|
||||
log.Printf("can't remove info: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, referer, http.StatusMovedPermanently)
|
||||
}
|
||||
func (a *AdminApi) uploadVideo(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
log.Printf("can't parse form: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
referer := r.Header.Get("Referer")
|
||||
reader, err := r.MultipartReader()
|
||||
if err != nil {
|
||||
log.Printf("can't read multipart: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for {
|
||||
header, err := reader.NextPart()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
defer header.Close()
|
||||
if header.FormName() != "file" {
|
||||
break
|
||||
}
|
||||
filename := header.FileName()
|
||||
if len(filename) == 0 {
|
||||
http.Error(w, "Filename are empty", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
tempFile, err := ioutil.TempFile(path.Join(config.DataDirectory, "tmp"), "upload-")
|
||||
if err != nil {
|
||||
log.Printf("can't create temp file: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer tempFile.Close()
|
||||
hashbucket := md5.New()
|
||||
fileWriter := io.MultiWriter(tempFile, hashbucket)
|
||||
written, err := io.Copy(fileWriter, header)
|
||||
if err != nil {
|
||||
log.Printf("can't write temp file: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
tmpName := path.Base(tempFile.Name())
|
||||
err = tempFile.Close()
|
||||
if err != nil {
|
||||
log.Printf("can't close file: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var video Video
|
||||
video.DisplayName = path.Base(path.Clean(filename))
|
||||
video.ID = fmt.Sprintf("%x", hashbucket.Sum(nil))
|
||||
video.Name = fmt.Sprintf("%s%s", video.ID, path.Ext(filename))
|
||||
video.Size = written
|
||||
err = os.Rename(path.Join(config.DataDirectory, "tmp", tmpName), path.Join(config.DataDirectory, "video", video.Name))
|
||||
if err != nil {
|
||||
log.Printf("can't rename file: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
infoRaw, err := json.Marshal(&video)
|
||||
if err != nil {
|
||||
log.Printf("can't marshal info: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(path.Join(config.DataDirectory, "video", video.ID+".json"), infoRaw, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Printf("can't write info: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, referer, http.StatusMovedPermanently)
|
||||
}
|
||||
func (a *AdminApi) renameVideo(w http.ResponseWriter, r *http.Request) {}
|
42
config.go
Normal file
42
config.go
Normal file
@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ApplicationName string `yaml:"application_name"`
|
||||
ApplicationPrefix string `yaml:"application_prefix"`
|
||||
DataDirectory string `yaml:"data_directory"`
|
||||
ListenAddress string `yaml:"listen_address"`
|
||||
AdminUser string `yaml:"admin_user"`
|
||||
AdminPassword string `yaml:"admin_password"`
|
||||
Generation int `yaml:"generation"`
|
||||
}
|
||||
|
||||
func (c *Config) ParseFile(path string) (err error) {
|
||||
configRaw, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("can't load configuration: %s", err.Error())
|
||||
return
|
||||
}
|
||||
err = yaml.UnmarshalStrict(configRaw, c)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("can't parse configuration: %s", err.Error())
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewConfig() *Config {
|
||||
return &Config{}
|
||||
}
|
||||
|
||||
func NewConfigParseFile(path string) (c *Config, err error) {
|
||||
c = NewConfig()
|
||||
err = c.ParseFile(path)
|
||||
return
|
||||
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module videopage
|
||||
|
||||
go 1.18
|
||||
|
||||
require gopkg.in/yaml.v2 v2.4.0 // indirect
|
3
go.sum
Normal file
3
go.sum
Normal file
@ -0,0 +1,3 @@
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
18
logger.go
Normal file
18
logger.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
h http.Handler
|
||||
}
|
||||
|
||||
func NewLogger(h http.Handler) (l *Logger) {
|
||||
return &Logger{h: h}
|
||||
}
|
||||
func (l *Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s %s%s", r.RemoteAddr, r.Method, r.Host, r.URL)
|
||||
l.h.ServeHTTP(w, r)
|
||||
}
|
195
main.go
Normal file
195
main.go
Normal file
@ -0,0 +1,195 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
templates *template.Template
|
||||
//go:embed templates
|
||||
fs embed.FS
|
||||
config *Config
|
||||
)
|
||||
|
||||
func main() {
|
||||
var configPath string
|
||||
var rescan bool
|
||||
flag.StringVar(&configPath, "config", "config.yaml", "Path to the configuration file")
|
||||
flag.BoolVar(&rescan, "rescan", false, "Rescan the data direcory")
|
||||
flag.Parse()
|
||||
var err error
|
||||
config, err = NewConfigParseFile(configPath)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if rescan {
|
||||
rescanFiles()
|
||||
return
|
||||
}
|
||||
|
||||
{
|
||||
entry, err := os.Stat(path.Join(config.DataDirectory, "tmp"))
|
||||
if os.IsNotExist(err) {
|
||||
err = os.Mkdir(path.Join(config.DataDirectory, "tmp"), os.ModeDir)
|
||||
if err != nil {
|
||||
log.Printf("FATAL: can't create tmp directory: %s", err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("FATAL: can't stat tmp directory: %s", err.Error())
|
||||
os.Exit(-2)
|
||||
}
|
||||
if !entry.IsDir() {
|
||||
log.Printf("FATAL: tmp path isn't directory")
|
||||
os.Exit(-3)
|
||||
}
|
||||
}
|
||||
|
||||
templates = template.Must(template.ParseFS(fs, "templates/*"))
|
||||
log.Printf("Loaded templates: %s", templates.DefinedTemplates())
|
||||
videoFS := http.FileServer(http.Dir(config.DataDirectory))
|
||||
adminMux := NewAdminApi()
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", indexHandler)
|
||||
mux.HandleFunc("/video/index.json", videoIndexHandler)
|
||||
mux.Handle("/video/", videoFS)
|
||||
mux.Handle("/admin/", http.StripPrefix("/admin", adminMux))
|
||||
if len(config.ApplicationPrefix) > 0 {
|
||||
http.ListenAndServe(config.ListenAddress, http.StripPrefix(config.ApplicationPrefix, NewLogger(mux)))
|
||||
} else {
|
||||
http.ListenAndServe(config.ListenAddress, NewLogger(mux))
|
||||
}
|
||||
}
|
||||
|
||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var v struct {
|
||||
ApplicationName string
|
||||
}
|
||||
v.ApplicationName = config.ApplicationName
|
||||
err := executeTemplate(w, "index.html", &v)
|
||||
if err != nil {
|
||||
log.Printf("Error executing temlate \"index.html\": %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func videoIndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var index VideoIndex
|
||||
index.Success = true
|
||||
index.Generation = config.Generation
|
||||
dir, err := os.ReadDir(path.Join(config.DataDirectory, "video"))
|
||||
if err != nil {
|
||||
log.Printf("can't red video directory: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for _, entry := range dir {
|
||||
if entry.Type().IsRegular() && path.Ext(entry.Name()) == ".json" && entry.Name() != "index.json" {
|
||||
var v Video
|
||||
rawInfo, err := ioutil.ReadFile(path.Join(config.DataDirectory, "video", entry.Name()))
|
||||
if err != nil {
|
||||
log.Printf("can't read info for %s: %s", entry.Name(), err.Error())
|
||||
continue
|
||||
}
|
||||
err = json.Unmarshal(rawInfo, &v)
|
||||
if err != nil {
|
||||
log.Printf("can't read info for %s: %s", entry.Name(), err.Error())
|
||||
continue
|
||||
}
|
||||
index.Videos = append(index.Videos, v)
|
||||
index.Count++
|
||||
}
|
||||
}
|
||||
indexRaw, err := json.Marshal(index)
|
||||
if err != nil {
|
||||
log.Printf("can't marshal index: %s", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(indexRaw)
|
||||
}
|
||||
|
||||
func executeTemplate(w http.ResponseWriter, name string, data interface{}) (err error) {
|
||||
var buf bytes.Buffer
|
||||
bufferRW := bufio.NewReadWriter(bufio.NewReader(&buf), bufio.NewWriter(&buf))
|
||||
err = templates.ExecuteTemplate(bufferRW, name, data)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
bufferRW.Flush()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.Copy(w, bufferRW)
|
||||
return
|
||||
}
|
||||
|
||||
func rescanFiles() {
|
||||
var index VideoIndex
|
||||
index.Generation = config.Generation
|
||||
index.Success = true
|
||||
dirPath := path.Join(config.DataDirectory, "video")
|
||||
dir, err := os.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
log.Printf("can't read data directory: %s", err.Error())
|
||||
}
|
||||
for _, entry := range dir {
|
||||
if entry.Type().IsRegular() {
|
||||
if !strings.HasSuffix(entry.Name(), ".json") {
|
||||
var v Video
|
||||
file, err := os.Open(path.Join(dirPath, entry.Name()))
|
||||
if err != nil {
|
||||
log.Printf("can't read info file: %s", err.Error())
|
||||
}
|
||||
v.Name = entry.Name()
|
||||
hashsum := md5.New()
|
||||
_, err = io.Copy(hashsum, file)
|
||||
if err != nil {
|
||||
log.Printf("can't read info file: %s", err.Error())
|
||||
}
|
||||
hashstring := fmt.Sprintf("%x", hashsum.Sum(nil))
|
||||
v.ID = hashstring
|
||||
ext := path.Ext(entry.Name())
|
||||
err = os.Rename(path.Join(dirPath, entry.Name()), path.Join(dirPath, string(hashstring)+ext))
|
||||
if err != nil {
|
||||
log.Printf("can't move file: %s", err.Error())
|
||||
}
|
||||
if info, _ := os.Stat(path.Join(dirPath, v.ID+".json")); info == nil {
|
||||
rawInfo, err := json.Marshal(&v)
|
||||
if err != nil {
|
||||
log.Printf("can't write info: %s", err.Error())
|
||||
}
|
||||
err = ioutil.WriteFile(path.Join(dirPath, v.ID+".json"), rawInfo, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Printf("can't write info: %s", err.Error())
|
||||
}
|
||||
}
|
||||
index.Videos = append(index.Videos, v)
|
||||
index.Count++
|
||||
}
|
||||
}
|
||||
}
|
||||
rawIndex, err := json.Marshal(&index)
|
||||
if err != nil {
|
||||
log.Fatalf("can't create index: %s", err)
|
||||
}
|
||||
err = ioutil.WriteFile(path.Join(dirPath, "index.json"), rawIndex, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalf("can't create index: %s", err)
|
||||
}
|
||||
}
|
41
templates/admin.html
Normal file
41
templates/admin.html
Normal file
@ -0,0 +1,41 @@
|
||||
{{define "admin.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.ApplicationName}} | Admin page</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: black;
|
||||
color: antiquewhite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="playlist">
|
||||
<p>Current playlist</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>name</td>
|
||||
<td>size</td>
|
||||
<td>preview</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Videos}}
|
||||
<tr>
|
||||
<td><form action="delete" method="post"><input id="fileid" name="fileid" type="hidden" value="{{.ID}}"><input value="Delete" type="submit"></form></td>
|
||||
<td>{{html .DisplayName}}</td>
|
||||
<td>{{html .Size}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
<form action="upload" method="post" enctype="multipart/form-data">
|
||||
<p>Add new: <input id="file" type="file" name="file" multiple> <input type="submit" ></p>
|
||||
</form>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
100
templates/index.html
Normal file
100
templates/index.html
Normal file
@ -0,0 +1,100 @@
|
||||
{{define "index.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.ApplicationName}}</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
.player-container .player {
|
||||
object-fit: cover;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
var player
|
||||
var generation = 0
|
||||
var current_track = -1
|
||||
var total_tracks = -1
|
||||
var track_list = []
|
||||
var last_known_time = -1
|
||||
options = {
|
||||
timeout: 5000
|
||||
}
|
||||
function load() {
|
||||
fetch("video/index.json", {timeout: 5000})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
console.log("failed to fetch index.json")
|
||||
return false
|
||||
}
|
||||
if (typeof data.generation =="number") {
|
||||
if (generation == 0) {
|
||||
generation = data.generation
|
||||
}
|
||||
if (data.generation > generation) {
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
total_tracks = data.count
|
||||
track_list = data.videos
|
||||
if (current_track == -1) {
|
||||
current_track = 0
|
||||
player.src = "video/" + track_list[current_track].name
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
function videoEnd(){
|
||||
if (total_tracks == -1) {
|
||||
return
|
||||
}
|
||||
if (player.ended) {
|
||||
nextVideo()
|
||||
}
|
||||
|
||||
}
|
||||
function nextVideo(){
|
||||
current_track++
|
||||
if (current_track > total_tracks-1) {
|
||||
current_track = 0
|
||||
}
|
||||
player.src = "video/" + track_list[current_track].name
|
||||
}
|
||||
function checkStall(){
|
||||
if (last_known_time == -1) {
|
||||
last_known_time = player.currentTime
|
||||
return
|
||||
}
|
||||
if (last_known_time == player.currentTime) {
|
||||
nextVideo()
|
||||
}
|
||||
}
|
||||
window.onload = function() {
|
||||
player = document.getElementById("player")
|
||||
player.autoplay = true
|
||||
player.controls = true
|
||||
load()
|
||||
setInterval(videoEnd, 1000)
|
||||
setInterval(load, 60000)
|
||||
setInterval(checkStall, 30000)
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript><h1 class="error">JavaScript is required for this page but not available</h1></noscript>
|
||||
<div id="player-container" class="player-container">
|
||||
<video id="player" class="player"></video>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
39
video.go
Normal file
39
video.go
Normal file
@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Video struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
ID string `json:"id"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type VideoIndex struct {
|
||||
Success bool `json:"success"`
|
||||
Generation int `json:"generation"`
|
||||
Count int `json:"count"`
|
||||
Videos []Video `json:"videos"`
|
||||
}
|
||||
|
||||
func readVideoInfo(filepath string) (video Video, err error) {
|
||||
if !strings.HasSuffix(filepath, ".json") {
|
||||
filepath = filepath + ".json"
|
||||
}
|
||||
infoRaw, err := ioutil.ReadFile(filepath)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("can't read infofile \"%s\": %s", filepath, err.Error())
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(infoRaw, &video)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("can't read infofile \"%s\": %s", filepath, err.Error())
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue
Block a user