This commit is contained in:
bel
2021-09-14 06:30:17 -06:00
commit 7ab1723a5e
327 changed files with 127104 additions and 0 deletions

50
justvjs/.gitignore vendored Executable file
View File

@@ -0,0 +1,50 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
*.aseprite
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Log files
*.log
# GoCode debug file
debug
# Linux binary
MovieNight
# Windows binary
MovieNight.exe
# Darwin binary
MovieNightDarwin
# Twitch channel info
static/subscriber.json
# This file now holds the stream key. Don't include it.
settings.json
# vscode
.vscode
# Autobuilt wasm files
static/main.wasm
# tags for vim
tags
# channel and emote list from twitch
subscribers.json
stream
**/*.sw*
justvjs

13
justvjs/Dockerfile Executable file
View File

@@ -0,0 +1,13 @@
FROM frolvlad/alpine-glibc:alpine-3.9_glibc-2.28
RUN apk update && apk add --no-cache ca-certificates bash
WORKDIR /main
COPY . .
ENV GOPATH=""
ENV MNT="/mnt/"
ENTRYPOINT ["/main/exec-justvjs"]
CMD []

129
justvjs/index.html Executable file
View File

@@ -0,0 +1,129 @@
<html>
<head>
<link href="/source/video-js.css" rel="stylesheet" />
<!-- If you'd like to support IE8 (for Video.js versions prior to v7) -->
<script src="/source/videojs-ie8.min.js"></script>
<style>
html {
background-color: #111;
}
body {
color: #ddd;
max-width: 100%;
height: 800px;
}
body * {
max-width: 100%;
max-height: 100%;
}
body > div > form {
display: inline-block;
}
</style>
<script>
function http(method, remote, callback, body) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
callback(xmlhttp.responseText, xmlhttp.status)
}
};
xmlhttp.open(method, remote, true);
if (typeof body == "undefined") {
body = null
}
xmlhttp.send(body);
}
function changeTo(f) {
function callback(responseBody, responseStatus) {
console.log("http ", address, "from", f.custom.value, " vs ", f.source[f.source.selectedIndex].value)
}
let address = f.custom.value
if (!address)
address = f.source[f.source.selectedIndex].value
console.log("http ", address, "from", f.custom.value, " vs ", f.source[f.source.selectedIndex].value)
http(
"post",
"/player/set",
callback,
JSON.stringify(
{
"address": address
}
),
)
}
function seekTo(f) {
let ts = f.ts.value
if (ts.match(/^[0-9]{2}:[0-9]{2}$/))
ts = "00:" + ts
function callback(responseBody, responseStatus) {console.log(ts, responseStatus, responseBody)}
http("post", "/player/seek", callback, JSON.stringify({"ts": ts}))
}
function pause(f) {
function callback(responseBody, responseStatus) {console.log(responseStatus, responseBody)}
http("post", "/player/pause", callback, JSON.stringify({}))
}
function resume(f) {
function callback(responseBody, responseStatus) {console.log(responseStatus, responseBody)}
http("post", "/player/resume", callback, JSON.stringify({}))
}
</script>
</head>
<div>
<form action="#" method="post" onsubmit="changeTo(this); return false;">
<select name="source">
<option value="192.168.0.83:8554">play room</option>
<option value="192.168.0.96:8554">living room</option>
<option value="192.168.0.97:8554">basement</option>
<option value="192.168.0.98:8554">garage</option>
</select>
<input type="text" name="custom"/>
<input type="submit"/>
</form>
<div style="display:inline-block; min-width: 1em;"></div>
<form action="#" method="post" onsubmit="pause(this); return false;">
<input value="pause" type="submit"/>
</form>
<form action="#" method="post" onsubmit="resume(this); return false;">
<input value="resume" type="submit"/>
</form>
<div style="display:inline-block; min-width: 1em;"></div>
<form action="#" method="post" onsubmit="seekTo(this); return false;">
<label for="ts">Like '[hh:]mm:ss'</label>
<input type="text" pattern="^[0-9]{2}:[0-9]{2}(:[0-9]{2})?$" name="ts"/>
<input type="submit"/>
</form>
</div>
<body>
<video
id="my-video"
class="video-js"
controls
preload="auto"
width="auto"
height="auto"
poster="MY_VIDEO_POSTER.jpg"
data-setup='{"liveui": true}'
autoplay
>
<source src="/stream/test.m3u8" />
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to a
web browser that
<a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
</p>
</video>
<script src="/source/video.js"></script>
</body>
</html>

239
justvjs/main.go Executable file
View File

@@ -0,0 +1,239 @@
package main
import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"local/args"
"log"
"net/http"
"os"
"os/exec"
"os/signal"
"path"
"syscall"
"time"
"golang.org/x/sys/unix"
)
var rtspCmd *exec.Cmd
var done bool
var as *args.ArgSet
var rtspAddr string
var ss string
func main() {
as = args.NewArgSet()
as.Append(args.INT, "p", "port to listen on", "8101")
as.Append(args.STRING, "d", "dir to serve", ".")
as.Append(args.STRING, "rtsp", "rtsp ip:port", "192.168.0.96:8554")
as.Append(args.STRING, "rtsp-pass", "rtsp password", "fwees123")
as.Append(args.STRING, "input", "input override", "")
as.Append(args.STRING, "hls", "hsl params", "-hls_time 3 -hls_list_size 10 -hls_flags +delete_segments+append_list+omit_endlist")
as.Append(args.STRING, "ss", "time to seek to", "00:00:00")
as.Append(args.INT, "kbps", "stream kbps", "500")
if err := as.Parse(); err != nil {
panic(err)
}
rtspAddr = as.GetString("rtsp")
ss = as.GetString("ss")
stream := path.Join(as.GetString("d"), "stream")
os.RemoveAll(stream)
if err := os.MkdirAll(stream, os.ModePerm); err != nil {
panic(err)
}
go rtsp()
for {
_, err := os.Stat(path.Join(stream, "test.m3u8"))
if err == nil {
break
} else if !os.IsNotExist(err) {
panic(err)
}
time.Sleep(time.Second * 1)
}
h := http.FileServer(http.Dir(as.GetString("d")))
go func() {
if err := http.ListenAndServe(fmt.Sprintf(":%d", as.GetInt("p")), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost && path.Dir(r.URL.Path) == "/player" {
switch path.Base(r.URL.Path) {
case "set":
var address struct {
Address string `json:"address"`
}
err := json.NewDecoder(r.Body).Decode(&address)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
rtspAddr = address.Address
killStream()
log.Printf("new rtsp: %s", rtspAddr)
case "seek":
var ts struct {
TS string `json:"ts"`
}
err := json.NewDecoder(r.Body).Decode(&ts)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ss = ts.TS
killStream()
log.Printf("new ss: %s", ss)
case "pause":
stopStream()
log.Printf("stopped stream")
case "resume":
startStream()
log.Printf("resumed stream")
default:
log.Printf("bad cmd: %q", r.URL.Path)
http.NotFound(w, r)
}
} else {
h.ServeHTTP(w, r)
}
})); err != nil {
panic(err)
}
}()
stop := make(chan os.Signal)
signal.Notify(stop, os.Interrupt)
<-stop
permaKillStream()
os.RemoveAll(stream)
}
func rtsp() {
log.Println("installing...")
install := exec.Command("bash", "-c", `
if ! which ffmpeg; then
apk add --no-cache ffmpeg \
|| sudo apk add --no-cache ffmpeg \
|| (apt update; apt -y install ffmpeg) \
|| (apt -y update; apt -y install ffmpeg) \
|| (sudo apt -y update; sudo apt -y install ffmpeg) \
|| (apt-get update; apt-get -y install ffmpeg) \
|| (apt-get -y update; apt-get -y install ffmpeg) \
|| (sudo apt-get -y update; sudo apt-get -y install ffmpeg) \
|| true
fi
if ! which ffmpeg; then
exit 499
fi
`)
if o, err := install.StdoutPipe(); err != nil {
panic(err)
} else {
go io.Copy(os.Stdout, o)
}
if o, err := install.StderrPipe(); err != nil {
panic(err)
} else {
go io.Copy(os.Stderr, o)
}
if err := install.Run(); err != nil {
panic(err)
}
log.Println("looping...")
for !done {
log.Println("streaming...")
i := `-rtsp_transport tcp -stimeout 10000000 -i rtsp://` + rtspAddr + `/unicast -an`
if input := as.GetString("input"); len(input) > 0 {
i = input
}
s := ""
if len(ss) > 0 {
s = "-ss " + ss
}
rtspCmd = exec.Command("bash", "-c", `
mkdir -p `+path.Join(as.GetString("d"), "stream")+`;
rm -rf `+path.Join(as.GetString("d"), "stream", "*")+`;
exec ffmpeg \
-re \
-nostats \
-loglevel warning \
-fflags nobuffer \
`+s+` \
`+i+` \
-preset ultrafast \
-c:v copy \
-b:v `+fmt.Sprint(as.GetInt("kbps"))+`k \
-flags +cgop \
-g 30 \
`+as.GetString("hls")+` \
`+path.Join(as.GetString("d"), "stream", "test.m3u8")+`
`)
if o, err := rtspCmd.StdoutPipe(); err != nil {
panic(err)
} else {
go io.Copy(os.Stdout, o)
}
if o, err := rtspCmd.StderrPipe(); err != nil {
panic(err)
} else {
go io.Copy(os.Stderr, o)
}
log.Println("starting rtsp cmd", rtspCmd)
if err := rtspCmd.Start(); err != nil {
panic(err)
}
log.Println("waiting rtsp cmd")
log.Println(rtspCmd.Wait())
}
}
func startStream() {
signalStream(syscall.Signal(unix.SIGCONT))
}
func stopStream() {
signalStream(syscall.Signal(unix.SIGSTOP))
}
func killStream() {
signalStream(syscall.Signal(unix.SIGKILL))
}
func permaKillStream() {
done = true
killStream()
}
func signalStream(s syscall.Signal) {
for rtspCmd == nil {
log.Println("rtspCmdis nil")
time.Sleep(time.Second * 3)
}
for rtspCmd.Process == nil {
log.Println("rtspCmd.Process is nil")
time.Sleep(time.Second * 3)
}
log.Println(s, rtspCmd.Process.Signal(os.Signal(s)))
}
func rebootCam() {
c := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
r, err := http.NewRequest("GET", "https://"+rtspAddr+"/cgi-bin/action.cgi?cmd=reboot", nil)
if err != nil {
panic(err)
}
pass := as.GetString("rtsp-pass")
r.SetBasicAuth("root", pass)
resp, err := c.Do(r)
if err != nil {
panic(err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
panic(resp.StatusCode)
}
}

1663
justvjs/source/video-js.css Executable file

File diff suppressed because one or more lines are too long

55692
justvjs/source/video.js Executable file

File diff suppressed because it is too large Load Diff

1
justvjs/source/videojs-ie8.min.js vendored Executable file

File diff suppressed because one or more lines are too long

14
justvjs/testdata/ffmpeg_live_stream_0 vendored Executable file
View File

@@ -0,0 +1,14 @@
$HOME/ffmpeg-local/ffmpeg \
-i rtsp://192.168.0.98:8554/unicast \
-preset ultrafast \
-c:v copy \
-c:a copy \
-bufsize 1835k \
-pix_fmt yuv420p \
-flags \
-global_header \
-hls_time 10 \
-hls_list_size 6 \
-hls_wrap 10 \
-start_number 1 \
./test.m3u8

8
justvjs/testdata/ffmpeg_live_stream_1 vendored Executable file
View File

@@ -0,0 +1,8 @@
$HOME/ffmpeg-local/ffmpeg \
-fflags nobuffer \
-rtsp_transport tcp \
-i rtsp://192.168.0.98:8554/unicast \
-flags +cgop \
-g 30 \
-hls_flags delete_segments \
./test.m3u8

11
justvjs/testdata/ffmpeg_live_stream_2 vendored Executable file
View File

@@ -0,0 +1,11 @@
$HOME/ffmpeg-local/ffmpeg \
-fflags nobuffer \
-rtsp_transport tcp \
-i rtsp://192.168.0.83:8554/unicast \
-preset ultrafast \
-c:v copy \
-an \
-flags +cgop \
-g 30 \
-hls_flags delete_segments \
./test.m3u8

45
justvjs/testdata/rtsp.html vendored Executable file
View File

@@ -0,0 +1,45 @@
<head>
<link href="https://vjs.zencdn.net/7.7.5/video-js.css" rel="stylesheet" />
<!-- If you'd like to support IE8 (for Video.js versions prior to v7) -->
<script src="https://vjs.zencdn.net/ie8/1.1.2/videojs-ie8.min.js"></script>
</head>
<body>
<video
id="my-video"
class="video-js"
controls
preload="auto"
width="640"
height="264"
poster="MY_VIDEO_POSTER.jpg"
data-setup="{}"
>
<source src="/public/test.m3u8" />
<!--
<source src="MY_VIDEO.mp4" type="video/mp4" />
<source src="MY_VIDEO.webm" type="video/webm" />
-->
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to a
web browser that
<a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
</p>
</video>
<script src="https://vjs.zencdn.net/7.7.5/video.js"></script>
</body>
<!--
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<video id="videoElement" controls autoplay x5-video-player-type="h5" x5-video-player-fullscreen="true" playsinline webkit-playsinline>
<--<source src="blob:http://192.168.0.86:8089/bf97e0d0-3d2d-4b3b-aeb1-e70e2fdb9f48" type="video/x-flv">
<source src="rtp://192.168.0.98:8554/unicast" >
Nope...
</video>
</body>
</html>
-->