1
0
mirror of https://gitlab.crans.org/nounous/ghostream.git synced 2025-07-03 04:32:46 +02:00

16 Commits

Author SHA1 Message Date
3493ba5e2f README: add better alternative 2023-08-20 18:50:21 +02:00
f7cf187bac Ignore stream name case 2021-02-25 17:45:36 +01:00
dc594d091c Ignore stream name case 2021-02-25 17:38:09 +01:00
a429216735 Allow uppercase letters in stream name 2021-02-25 17:23:11 +01:00
e6fd4f6352 Use streamid id option, wrong documentation 2021-01-12 00:06:47 +01:00
34652f8f3e I am an idiot, don't let only people with the *wrong* password stream 2021-01-08 23:05:01 +01:00
79f52ed880 Log the LDAP connection 2021-01-08 22:56:19 +01:00
ee16bf9e21 Alias is not properly replaced 2021-01-08 22:46:14 +01:00
e47aefd6df Replace the name of the stream if using an alias 2021-01-08 22:23:33 +01:00
7e0ee7aba5 Match aliases as groups 2021-01-03 05:07:25 +01:00
8d2adad509 Avoid infinite loop 2020-12-06 13:41:17 +01:00
0035c63c22 Add aliases auth support if the authentication method is LDAP 2020-12-06 13:36:24 +01:00
849196b4cb Add DASH player source for more compatibility 2020-11-20 03:26:10 +01:00
205c4b526c Upgrade ovenplayer, add HTML5 provider 2020-11-20 02:46:25 +01:00
1d117ea480 Config on legal mentions 2020-11-12 01:42:28 +01:00
45cb61e436 Merge branch 'ovenmediaengine' into 'dev'
OvenMediaEngine

See merge request nounous/ghostream!8
2020-11-09 21:53:44 +01:00
22 changed files with 120 additions and 39 deletions

View File

@ -11,6 +11,13 @@
This project was developped at [Cr@ns](https://crans.org/) to stream events. This project was developped at [Cr@ns](https://crans.org/) to stream events.
> **Note**
> *This project is no longer maintained!*
>
> As an alternative, you should try [Galène](https://galene.org/) which supports [WebRTC-HTTP ingestion protocol (WHIP)](https://datatracker.ietf.org/doc/draft-ietf-wish-whip/) for low-latency streaming.
> OBS Studio introduced WHIP output in version 30.0.
> Galène supports WHIP since Galène 0.8.
Features: Features:
- WebRTC playback with a lightweight web interface. - WebRTC playback with a lightweight web interface.

View File

@ -20,7 +20,7 @@ type Options struct {
// Backend to log user in // Backend to log user in
type Backend interface { type Backend interface {
Login(string, string) (bool, error) Login(string, string) (bool, string, error)
Close() Close()
} }

View File

@ -23,15 +23,15 @@ type Basic struct {
// Login hashs password and compare // Login hashs password and compare
// Returns (true, nil) if success // Returns (true, nil) if success
func (a Basic) Login(username string, password string) (bool, error) { func (a Basic) Login(username string, password string) (bool, string, error) {
hash, ok := a.Cfg.Credentials[username] hash, ok := a.Cfg.Credentials[username]
if !ok { if !ok {
return false, errors.New("user not found in credentials") return false, "", errors.New("user not found in credentials")
} }
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
// Login succeeded if no error // Login succeeded if no error
return err == nil, err return err == nil, username, err
} }
// Close has no connection to close // Close has no connection to close

View File

@ -10,19 +10,19 @@ func TestBasicLogin(t *testing.T) {
// Test good credentials // Test good credentials
backend, _ := New(&Options{Credentials: basicCredentials}) backend, _ := New(&Options{Credentials: basicCredentials})
ok, err := backend.Login("demo", "demo") ok, _, err := backend.Login("demo", "demo")
if !ok { if !ok {
t.Error("Error while logging with the basic authentication:", err) t.Error("Error while logging with the basic authentication:", err)
} }
// Test bad username // Test bad username
ok, err = backend.Login("baduser", "demo") ok, _, err = backend.Login("baduser", "demo")
if ok { if ok {
t.Error("Authentification failed to fail:", err) t.Error("Authentification failed to fail:", err)
} }
// Test bad password // Test bad password
ok, err = backend.Login("demo", "badpass") ok, _, err = backend.Login("demo", "badpass")
if ok { if ok {
t.Error("Authentification failed to fail:", err) t.Error("Authentification failed to fail:", err)
} }

View File

@ -3,10 +3,13 @@ package ldap
import ( import (
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"log"
"strings"
) )
// Options holds package configuration // Options holds package configuration
type Options struct { type Options struct {
Aliases map[string]map[string]string
URI string URI string
UserDn string UserDn string
} }
@ -19,13 +22,37 @@ type LDAP struct {
// Login tries to bind to LDAP // Login tries to bind to LDAP
// Returns (true, nil) if success // Returns (true, nil) if success
func (a LDAP) Login(username string, password string) (bool, error) { func (a LDAP) Login(username string, password string) (bool, string, error) {
aliasSplit := strings.SplitN(username, "__", 2)
potentialUsernames := []string{username}
if len(aliasSplit) == 2 {
alias := aliasSplit[0]
trueUsername := aliasSplit[1]
// Resolve stream alias if necessary
if aliases, ok := a.Cfg.Aliases[alias]; ok {
if _, ok := aliases[trueUsername]; ok {
log.Printf("[LDAP] Use stream alias %s for username %s", alias, trueUsername)
potentialUsernames = append(potentialUsernames, trueUsername)
}
}
}
var err error = nil
for _, username := range potentialUsernames {
// Try to bind as user // Try to bind as user
bindDn := "cn=" + username + "," + a.Cfg.UserDn bindDn := "cn=" + username + "," + a.Cfg.UserDn
err := a.Conn.Bind(bindDn, password) log.Printf("[LDAP] Logging to %s...", bindDn)
err = a.Conn.Bind(bindDn, password)
if err == nil {
// Login succeeded if no error // Login succeeded if no error
return err == nil, err return true, aliasSplit[0], nil
}
}
log.Printf("[LDAP] Logging failed: %s", err)
// Unable to log in
return err == nil, "", err
} }
// Close LDAP connection // Close LDAP connection

View File

@ -23,6 +23,9 @@
<HLS> <HLS>
<Port>80</Port> <Port>80</Port>
</HLS> </HLS>
<DASH>
<Port>80</Port>
</DASH>
</Publishers> </Publishers>
</Bind> </Bind>
@ -52,14 +55,15 @@
</Video> </Video>
</Encode> </Encode>
<Encode> <Encode>
<Name>BYPASS</Name> <Name>bypass</Name>
<Video>
<Bypass>true</Bypass>
</Video>
<Audio> <Audio>
<Bypass>true</Bypass> <Bypass>true</Bypass>
</Audio> </Audio>
<Video>
<Bypass>true</Bypass>
</Video>
</Encode> </Encode>
</Encodes> </Encodes>
<Streams> <Streams>
<Stream> <Stream>
@ -71,9 +75,10 @@
<Stream> <Stream>
<Name>${OriginStreamName}_bypass</Name> <Name>${OriginStreamName}_bypass</Name>
<Profiles> <Profiles>
<Profile>BYPASS</Profile> <Profile>bypass</Profile>
</Profiles> </Profiles>
</Stream> </Stream>
</Streams> </Streams>
<Providers> <Providers>
<RTMP> <RTMP>
@ -86,12 +91,25 @@
<Timeout>30000</Timeout> <Timeout>30000</Timeout>
</WebRTC> </WebRTC>
<HLS> <HLS>
<SegmentDuration>5</SegmentDuration> <SegmentDuration>2</SegmentDuration>
<SegmentCount>2</SegmentCount> <SegmentCount>2</SegmentCount>
<CrossDomain> <CrossDomain>
<Url>*</Url> <Url>*</Url>
</CrossDomain> </CrossDomain>
</HLS> </HLS>
<DASH>
<SegmentDuration>2</SegmentDuration>
<SegmentCount>2</SegmentCount>
<CrossDomain>
<Url>*</Url>
</CrossDomain>
</DASH>
<LLDASH>
<SegmentDuration>2</SegmentDuration>
<CrossDomain>
<Url>*</Url>
</CrossDomain>
</LLDASH>
</Publishers> </Publishers>
</Application> </Application>
</Applications> </Applications>

View File

@ -34,6 +34,13 @@ auth:
#ldap: #ldap:
# uri: ldap://127.0.0.1:389 # uri: ldap://127.0.0.1:389
# userdn: cn=users,dc=example,dc=com # userdn: cn=users,dc=example,dc=com
#
# # You can define aliases, to stream on stream.example.com/example with the credentials of the demo account.
# # You will have to use the streamid example__demo:password
# aliases:
# example:
# demo: ignored
#
## Stream forwarding ## ## Stream forwarding ##
# Forward an incoming stream to other servers # Forward an incoming stream to other servers

View File

@ -42,6 +42,7 @@ func New() *Config {
Credentials: make(map[string]string), Credentials: make(map[string]string),
}, },
LDAP: ldap.Options{ LDAP: ldap.Options{
Aliases: make(map[string]map[string]string),
URI: "ldap://127.0.0.1:389", URI: "ldap://127.0.0.1:389",
UserDn: "cn=users,dc=example,dc=com", UserDn: "cn=users,dc=example,dc=com",
}, },

View File

@ -79,20 +79,22 @@ func Serve(streams *messaging.Streams, authBackend auth.Backend, cfg *Options) {
if len(split) > 1 { if len(split) > 1 {
// password was provided so it is a streamer // password was provided so it is a streamer
name, password := split[0], split[1] name, password := strings.ToLower(split[0]), split[1]
if authBackend != nil { if authBackend != nil {
// check password // check password
if ok, err := authBackend.Login(name, password); !ok || err != nil { ok, username, err := authBackend.Login(name, password)
if !ok || err != nil {
log.Printf("Failed to authenticate for stream %s", name) log.Printf("Failed to authenticate for stream %s", name)
s.Close() s.Close()
continue continue
} }
name = username
} }
go handleStreamer(s, streams, name) go handleStreamer(s, streams, name)
} else { } else {
// password was not provided so it is a viewer // password was not provided so it is a viewer
name := split[0] name := strings.ToLower(split[0])
// Send stream // Send stream
go handleViewer(s, streams, name) go handleViewer(s, streams, name)

View File

@ -21,7 +21,7 @@ import (
var ( var (
// Precompile regex // Precompile regex
validPath = regexp.MustCompile("^/[a-z0-9@_-]*$") validPath = regexp.MustCompile("^/[a-zA-Z0-9@_-]*$")
counterMutex = new(sync.Mutex) counterMutex = new(sync.Mutex)
connectedClients = make(map[string]map[string]int64) connectedClients = make(map[string]map[string]int64)
@ -42,7 +42,7 @@ func viewerHandler(w http.ResponseWriter, r *http.Request) {
} }
// Get stream ID from URL, or from domain name // Get stream ID from URL, or from domain name
path := r.URL.Path[1:] path := strings.ToLower(r.URL.Path[1:])
host := r.Host host := r.Host
if strings.Contains(host, ":") { if strings.Contains(host, ":") {
realHost, _, err := net.SplitHostPort(r.Host) realHost, _, err := net.SplitHostPort(r.Host)
@ -58,7 +58,7 @@ func viewerHandler(w http.ResponseWriter, r *http.Request) {
if path == "about" { if path == "about" {
path = "" path = ""
} else { } else {
path = streamID path = strings.ToLower(streamID)
} }
} }
@ -97,6 +97,7 @@ func staticHandler() http.Handler {
func statisticsHandler(w http.ResponseWriter, r *http.Request) { func statisticsHandler(w http.ResponseWriter, r *http.Request) {
// Retrieve stream name from URL // Retrieve stream name from URL
name := strings.SplitN(strings.Replace(r.URL.Path[7:], "/", "", -1), "@", 2)[0] name := strings.SplitN(strings.Replace(r.URL.Path[7:], "/", "", -1), "@", 2)[0]
name = strings.ToLower(name)
userCount := 0 userCount := 0
// Clients have a unique generated identifier per session, that expires in 40 seconds. // Clients have a unique generated identifier per session, that expires in 40 seconds.

View File

@ -47,10 +47,20 @@ export function initViewerPage(stream, omeApp, viewersCounterRefreshPeriod, post
"label": " WebRTC - Source" "label": " WebRTC - Source"
}, },
{ {
"type": "hls",
"file": "https://" + window.location.host + "/" + omeApp + "/" + stream + "_bypass/playlist.m3u8", "file": "https://" + window.location.host + "/" + omeApp + "/" + stream + "_bypass/playlist.m3u8",
"type": "hls",
"label": " HLS" "label": " HLS"
} },
{
"file": "https://" + window.location.host + "/" + omeApp + "/" + stream + "_bypass/manifest.mpd",
"type": "dash",
"label": "DASH"
},
{
"file": "https://" + window.location.host + "/" + omeApp + "/" + stream + "_bypass/manifest_ll.mpd",
"type": "dash",
"label": "LL-DASH"
},
] ]
}); });
player.on("stateChanged", function (data) { player.on("stateChanged", function (data) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
/*! OvenPlayerv0.9.0 | (c)2020 AirenSoft Co., Ltd. | MIT license (https://github.com/AirenSoft/OvenPlayerPrivate/blob/master/LICENSE) | Github : https://github.com/AirenSoft/OvenPlayer */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
/*! OvenPlayerv0.9.0 | (c)2020 AirenSoft Co., Ltd. | MIT license (https://github.com/AirenSoft/OvenPlayerPrivate/blob/master/LICENSE) | Github : https://github.com/AirenSoft/OvenPlayer */

View File

@ -25,7 +25,7 @@
<ul> <ul>
<li> <li>
<b>Serveur :</b> <b>Serveur :</b>
<code>srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?IDENTIFIANT:MOT_DE_PASSE</code>, <code>srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid=IDENTIFIANT:MOT_DE_PASSE</code>,
avec <code>IDENTIFIANT</code> et <code>MOT_DE_PASSE</code> avec <code>IDENTIFIANT</code> et <code>MOT_DE_PASSE</code>
vos identifiants. vos identifiants.
</li> </li>

View File

@ -36,6 +36,7 @@
{{if .OMECfg.Enabled}} {{if .OMECfg.Enabled}}
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dashjs/2.9.3/dash.all.min.js"></script>
<script src="/static/ovenplayer/ovenplayer.js"></script> <script src="/static/ovenplayer/ovenplayer.js"></script>
<script src="/static/js/ovenplayer.js"></script> <script src="/static/js/ovenplayer.js"></script>
{{end}} {{end}}