Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d1b29275d7 | |||
| 7560bcbc83 | |||
| 090c360747 | |||
| a81bf0daa8 | |||
| c7128897b8 | |||
| 07def5ba04 | |||
| b7f4c63517 | |||
| 92c67df7b4 |
@@ -130,7 +130,7 @@ streams:
|
||||
|
||||
You can get any stream or file or device via FFmpeg and push it to go2rtc. The app will automatically start FFmpeg with the proper arguments when someone starts watching the stream.
|
||||
|
||||
Format: `ffmpeg:{input}#{params}`. Examples:
|
||||
Format: `ffmpeg:{input}#{param1}#{param2}#{param3}`. Examples:
|
||||
|
||||
```yaml
|
||||
streams:
|
||||
@@ -141,7 +141,7 @@ streams:
|
||||
file2: ffmpeg:~/media/BigBuckBunny.mp4#video=h264
|
||||
|
||||
# [FILE] video will be copied, audio will be transcoded to pcmu
|
||||
file3: ffmpeg:~/media/BigBuckBunny.mp4#video=copy&audio=pcmu
|
||||
file3: ffmpeg:~/media/BigBuckBunny.mp4#video=copy#audio=pcmu
|
||||
|
||||
# [HLS] video will be copied, audio will be skipped
|
||||
hls: ffmpeg:https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/gear5/prog_index.m3u8#video=copy
|
||||
@@ -150,7 +150,7 @@ streams:
|
||||
mjpeg: ffmpeg:http://185.97.122.128/cgi-bin/faststream.jpg?stream=half&fps=15#video=h264
|
||||
|
||||
# [RTSP] video and audio will be copied
|
||||
rtsp: ffmpeg:rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0#video=copy&audio=copy
|
||||
rtsp: ffmpeg:rtsp://rtsp:12345678@192.168.1.123/av_stream/ch0#video=copy#audio=copy
|
||||
```
|
||||
|
||||
All trascoding formats has built-in templates. But you can override them via YAML config. You can also add your own formats to config and use them with source params.
|
||||
|
||||
@@ -16,4 +16,7 @@ RUN if [ "${BUILD_ARCH}" = "aarch64" ]; then BUILD_ARCH="arm64"; \
|
||||
&& curl $(curl -s "https://raw.githubusercontent.com/ngrok/docker-ngrok/main/releases.json" | jq -r ".${BUILD_ARCH}.url") -o ngrok.zip \
|
||||
&& unzip ngrok
|
||||
|
||||
CMD [ "/app/go2rtc", "-config", "/config/go2rtc.yaml" ]
|
||||
COPY run.sh /
|
||||
RUN chmod a+x /run.sh
|
||||
|
||||
CMD [ "/run.sh" ]
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
|
||||
set +e
|
||||
|
||||
while true; do
|
||||
if [ -x /config/go2rtc ]; then
|
||||
/config/go2rtc -config /config/go2rtc.yaml
|
||||
else
|
||||
/app/go2rtc -config /config/go2rtc.yaml
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
+29
-6
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
@@ -38,7 +40,8 @@ func Init() {
|
||||
HandleFunc("/api/frame.mp4", frameHandler)
|
||||
HandleFunc("/api/frame.raw", frameHandler)
|
||||
HandleFunc("/api/stack", stackHandler)
|
||||
HandleFunc("/api/stats", statsHandler)
|
||||
HandleFunc("/api/streams", streamsHandler)
|
||||
HandleFunc("/api/exit", exitHandler)
|
||||
HandleFunc("/api/ws", apiWS)
|
||||
|
||||
// ensure we can listen without errors
|
||||
@@ -69,19 +72,39 @@ var basePath string
|
||||
var log zerolog.Logger
|
||||
var wsHandlers = make(map[string]WSHandler)
|
||||
|
||||
func statsHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
v := map[string]interface{}{
|
||||
"streams": streams.All(),
|
||||
func streamsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
src := r.URL.Query().Get("src")
|
||||
|
||||
switch r.Method {
|
||||
case "PUT":
|
||||
streams.Get(src)
|
||||
return
|
||||
case "DELETE":
|
||||
streams.Delete(src)
|
||||
return
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
if src != "" {
|
||||
v = streams.Get(src)
|
||||
} else {
|
||||
v = streams.All()
|
||||
}
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("[api.stats] marshal")
|
||||
log.Error().Err(err).Msg("[api.streams] marshal")
|
||||
}
|
||||
if _, err = w.Write(data); err != nil {
|
||||
log.Error().Err(err).Msg("[api.stats] write")
|
||||
log.Error().Err(err).Msg("[api.streams] write")
|
||||
}
|
||||
}
|
||||
|
||||
func exitHandler(w http.ResponseWriter, r *http.Request) {
|
||||
s := r.URL.Query().Get("code")
|
||||
code, _ := strconv.Atoi(s)
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func apiWS(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := new(Context)
|
||||
if err := ctx.Upgrade(w, r); err != nil {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
func initStatic(staticDir string) {
|
||||
var root http.FileSystem
|
||||
if staticDir != "" {
|
||||
log.Info().Str("dir", staticDir).Msg("[api] serve static")
|
||||
root = http.Dir(staticDir)
|
||||
} else {
|
||||
root = http.FS(www.Static)
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ func Handle(url string) (streamer.Producer, error) {
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(time.Second * 10):
|
||||
case <-time.After(time.Second * 15):
|
||||
_ = cmd.Process.Kill()
|
||||
log.Error().Str("url", url).Msg("[exec] timeout")
|
||||
return nil, errors.New("timeout")
|
||||
|
||||
+14
-1
@@ -54,7 +54,7 @@ func Init() {
|
||||
|
||||
var query url.Values
|
||||
if i := strings.IndexByte(s, '#'); i > 0 {
|
||||
query, _ = url.ParseQuery(s[i+1:])
|
||||
query = parseQuery(s[i+1:])
|
||||
s = s[:i]
|
||||
}
|
||||
|
||||
@@ -110,3 +110,16 @@ func Init() {
|
||||
return exec.Handle(s)
|
||||
})
|
||||
}
|
||||
|
||||
func parseQuery(s string) map[string][]string {
|
||||
query := map[string][]string{}
|
||||
for _, key := range strings.Split(s, "#") {
|
||||
var value string
|
||||
i := strings.IndexByte(key, '=')
|
||||
if i > 0 {
|
||||
key, value = key[:i], key[i+1:]
|
||||
}
|
||||
query[key] = append(query[key], value)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ func Get(name string) *Stream {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Delete(name string) {
|
||||
delete(streams, name)
|
||||
}
|
||||
|
||||
func All() map[string]interface{} {
|
||||
all := map[string]interface{}{}
|
||||
for name, stream := range streams {
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/AlexxIT/go2rtc/pkg/streamer"
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
@@ -57,7 +58,8 @@ func (c *Conn) Init() {
|
||||
}
|
||||
}
|
||||
|
||||
panic("something wrong")
|
||||
fmt.Printf("TODO: webrtc ontrack %+v\n", remote)
|
||||
fmt.Printf("TODO: webrtc ontrack %#v\n", remote)
|
||||
})
|
||||
|
||||
c.Conn.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
@SET GOOS=linux
|
||||
@SET GOARCH=amd64
|
||||
cd ..
|
||||
go build -ldflags "-s -w" -trimpath && upx-3.96 go2rtc
|
||||
+5
-1
@@ -46,4 +46,8 @@ pc.ontrack = ev => {
|
||||
|
||||
video.srcObject = ev.streams[0];
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## Useful links
|
||||
|
||||
- https://divtable.com/table-styler/
|
||||
+87
-25
@@ -1,44 +1,106 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
|
||||
<title>go2rtc</title>
|
||||
|
||||
<style>
|
||||
table {
|
||||
background-color: white;
|
||||
text-align: left;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
border: 1px solid black;
|
||||
padding: 5px 5px;
|
||||
}
|
||||
|
||||
table tbody td {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
table thead {
|
||||
background: #CFCFCF;
|
||||
background: linear-gradient(to bottom, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
|
||||
border-bottom: 3px solid black;
|
||||
}
|
||||
|
||||
table thead th {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 5px 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header"></div>
|
||||
<table id="items"></table>
|
||||
<div class="header">
|
||||
<input id="src" type="text" placeholder="url">
|
||||
<a id="add" href="#">add</a>
|
||||
</div>
|
||||
<table id="streams">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Online</th>
|
||||
<th>Commands</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<script>
|
||||
const baseUrl = location.origin + location.pathname.substr(
|
||||
0, location.pathname.lastIndexOf("/")
|
||||
);
|
||||
|
||||
const header = document.getElementById('header');
|
||||
header.innerHTML = `<a href="api/stats">stats</a>`;
|
||||
|
||||
const links = [
|
||||
'<a href="webrtc-async.html?url={name}">webrtc-async</a>',
|
||||
// '<a href="webrtc-sync.html?url={name}">webrtc-sync</a>',
|
||||
'<a href="api/frame.mp4?url={name}">frame.mp4</a>',
|
||||
'<a href="api/frame.raw?url={name}">frame.raw</a>',
|
||||
'<a href="webrtc.html?url={name}">webrtc</a>',
|
||||
'<a href="mse.html?url={name}">mse</a>',
|
||||
'<a href="api/frame.mp4?url={name}">frame.mp4</a>',
|
||||
'<a href="api/streams?src={name}">info</a>',
|
||||
];
|
||||
|
||||
fetch(`${baseUrl}/api/stats`).then(r => {
|
||||
r.json().then(data => {
|
||||
const content = document.getElementById('items');
|
||||
function reload() {
|
||||
fetch(`${baseUrl}/api/streams`).then(r => {
|
||||
r.json().then(data => {
|
||||
let html = '';
|
||||
|
||||
for (let name in data.streams) {
|
||||
let html = `<tr><td>${name || 'default'}</td>`;
|
||||
links.forEach(link => {
|
||||
html += `<td>${link.replace('{name}', name)}</td>`
|
||||
})
|
||||
html += `</tr>`;
|
||||
content.innerHTML += html
|
||||
}
|
||||
});
|
||||
})
|
||||
for (const [name, value] of Object.entries(data)) {
|
||||
const online = value !== null ? value.length : 0
|
||||
html += `<tr><td>${name || 'default'}</td><td>${online}</td><td>`;
|
||||
links.forEach(link => {
|
||||
html += link.replace('{name}', encodeURIComponent(name)) + ' ';
|
||||
})
|
||||
html += `<a href="#" onclick="deleteStream('${name}')">delete</a>`;
|
||||
html += `</td></tr>`;
|
||||
}
|
||||
|
||||
let content = document.getElementById('streams').getElementsByTagName('tbody')[0];
|
||||
content.innerHTML = html
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function deleteStream(src) {
|
||||
fetch(`${baseUrl}/api/streams?src=${encodeURIComponent(src)}`, {method: 'DELETE'}).then(reload);
|
||||
}
|
||||
|
||||
const addButton = document.querySelector('a#add');
|
||||
addButton.onclick = () => {
|
||||
let src = document.querySelector('input#src');
|
||||
fetch(`${baseUrl}/api/streams?src=${encodeURIComponent(src.value)}`, {method: 'PUT'}).then(reload);
|
||||
}
|
||||
|
||||
reload();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user