1
0
mirror of https://gitlab.crans.org/nounous/ghostream.git synced 2025-07-01 16:51:16 +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
23 changed files with 156 additions and 50 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,12 +3,15 @@ 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 {
URI string Aliases map[string]map[string]string
UserDn string URI string
UserDn string
} }
// LDAP authentification backend // LDAP authentification backend
@ -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) {
// Try to bind as user aliasSplit := strings.SplitN(username, "__", 2)
bindDn := "cn=" + username + "," + a.Cfg.UserDn potentialUsernames := []string{username}
err := a.Conn.Bind(bindDn, password)
// Login succeeded if no error if len(aliasSplit) == 2 {
return err == nil, err 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
bindDn := "cn=" + username + "," + a.Cfg.UserDn
log.Printf("[LDAP] Logging to %s...", bindDn)
err = a.Conn.Bind(bindDn, password)
if err == nil {
// Login succeeded if no error
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

@ -21,8 +21,11 @@
</IceCandidates> </IceCandidates>
</WebRTC> </WebRTC>
<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,15 +91,28 @@
<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>
</VirtualHost> </VirtualHost>
</VirtualHosts> </VirtualHosts>
</Server> </Server>

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
@ -173,6 +180,18 @@ web:
# #
#widgetURL: "" #widgetURL: ""
# IMPORTANT, CHANGE THIS
# You need to declare which entity you are and to specify an address to claim some content.
legalMentionsEntity: "l'association Crans"
legalMentionsAddress: "61 Avenue du Président Wilson, 94235 Cachan Cedex, France"
legalMentionsFullAddress:
- Association Cr@ns - ENS Paris-Saclay
- Notification de Contenus Illicites
- 4, avenue des Sciences
- 91190 Gif-sur-Yvette
- France
legalMentionsEmail: "bureau[at]crans.org"
## WebRTC server ## ## WebRTC server ##
webrtc: webrtc:
# If you disable webrtc module, the web client won't be able to play streams. # If you disable webrtc module, the web client won't be able to play streams.

View File

@ -42,8 +42,9 @@ func New() *Config {
Credentials: make(map[string]string), Credentials: make(map[string]string),
}, },
LDAP: ldap.Options{ LDAP: ldap.Options{
URI: "ldap://127.0.0.1:389", Aliases: make(map[string]map[string]string),
UserDn: "cn=users,dc=example,dc=com", URI: "ldap://127.0.0.1:389",
UserDn: "cn=users,dc=example,dc=com",
}, },
}, },
Forwarding: make(map[string][]string), Forwarding: make(map[string][]string),
@ -82,6 +83,11 @@ func New() *Config {
MapDomainToStream: make(map[string]string), MapDomainToStream: make(map[string]string),
PlayerPoster: "/static/img/no_stream.svg", PlayerPoster: "/static/img/no_stream.svg",
ViewersCounterRefreshPeriod: 20000, ViewersCounterRefreshPeriod: 20000,
LegalMentionsEntity: "l'association Crans",
LegalMentionsAddress: "61 Avenue du Président Wilson, 94235 Cachan Cedex, France",
LegalMentionsFullAddress: []string{"Association Cr@ns - ENS Paris-Saclay",
"Notification de Contenus Illicites", "4, avenue des Sciences", "91190 Gif-sur-Yvette", "France"},
LegalMentionsEmail: "bureau[at]crans.org",
}, },
WebRTC: webrtc.Options{ WebRTC: webrtc.Options{
Enabled: false, Enabled: false,

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

@ -9,7 +9,11 @@
</p> </p>
<h2>Comment je diffuse ?</h2> <h2>Comment je diffuse ?</h2>
<p>Pour diffuser un contenu vous devez être adhérent Crans.</p> <p>
Pour diffuser un contenu vous devez avoir des identifiants valides.
Si le service est hébergé par une association, il est probable que
vous deviez être membre de cette association.
</p>
<h3>Avec Open Broadcaster Software</h3> <h3>Avec Open Broadcaster Software</h3>
<p> <p>
@ -21,7 +25,7 @@
<ul> <ul>
<li> <li>
<b>Serveur :</b> <b>Serveur :</b>
<code>srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?IDENTIFIANT:MOT_DE_PASS</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>
@ -42,7 +46,8 @@
<p> <p>
<code> <code>
{{/* FIXME replace with good SRT params */}} {{/* FIXME replace with good SRT params */}}
ffmpeg -re -i mavideo.webm -vcodec libx264 -vprofile baseline ffmpeg -re -i mavideo.webm -vcodec libx264
-preset:v veryfast -vprofile baseline -tune zerolatency
-acodec aac -strict -2 -f flv -acodec aac -strict -2 -f flv
srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid=IDENTIFIANT:MOT_DE_PASSE srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid=IDENTIFIANT:MOT_DE_PASSE
</code> </code>
@ -95,10 +100,9 @@
Bien que VLC supporte officiellement le protocole SRT, Bien que VLC supporte officiellement le protocole SRT,
toutes les options ne sont pas encore implémentées, toutes les options ne sont pas encore implémentées,
notamment l'option pour choisir son stream. notamment l'option pour choisir son stream.
<a href="https://patches.videolan.org/patch/30299/">Un patch</a> Cette option n'est supportée que dans la version de développement
a été soumis et est en attente d'acceptation. depuis très récemment, grâce à un patch de l'un des développeurs
Une fois le patch accepté, il sera appliqué dans les versions de Ghostream. Sous Arch Linux, il suffit de récupérer
de développement de VLC. Sous Arch Linux, il suffit de récupérer
le paquet <code>vlc-git</code> de l'AUR. Avec un VLC à jour, le paquet <code>vlc-git</code> de l'AUR. Avec un VLC à jour,
il suffit d'exécuter : il suffit d'exécuter :
</p> </p>
@ -128,18 +132,18 @@
Le service de diffusion vidéo du Crans est un service d'hébergement Le service de diffusion vidéo du Crans est un service d'hébergement
au sens de l'article 6, I, 2e de la loi 2004-575 du 21 juin 2004. au sens de l'article 6, I, 2e de la loi 2004-575 du 21 juin 2004.
Conformément aux dispositions de l'article 6, II du même, Conformément aux dispositions de l'article 6, II du même,
l'association Crans conserve les données de nature à permettre conserve les données de nature à permettre
l'identification des auteurs du contenu diffusé. l'identification des auteurs du contenu diffusé.
Ce service est hébergé par l'association Crans, au Ce service est hébergé par {{.Cfg.LegalMentionsEntity}}, au
61 Avenue du Président Wilson, 94235 Cachan Cedex, France. {{.Cfg.LegalMentionsAddress}}.
</p> </p>
<p> <p>
<b>En cas de réclamation sur le contenu diffusé</b>, <b>En cas de réclamation sur le contenu diffusé</b>,
la loi vous autorise à contacter directement l'hébergeur à la loi vous autorise à contacter directement l'hébergeur à
l'adresse suivante : l'adresse suivante :
<pre>Association Cr@ns - ENS Paris-Saclay<br/>Notification de Contenus Illicites<br/>4, avenue des Sciences<br/>91190 Gif-sur-Yvette<br/>France</pre> <pre>{{range $i, $element := .Cfg.LegalMentionsFullAddress}}{{$element}}<br/>{{end}}</pre>
Vous pouvez également envoyer directement vos réclamations par Vous pouvez également envoyer directement vos réclamations par
courrier électronique à l'adresse <code>bureau[at]crans.org</code>. courrier électronique à l'adresse <code>{{.Cfg.LegalMentionsEmail}}</code>.
</p> </p>
</div> </div>
{{end}} {{end}}

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}}

View File

@ -28,6 +28,10 @@ type Options struct {
STUNServers []string STUNServers []string
ViewersCounterRefreshPeriod int ViewersCounterRefreshPeriod int
WidgetURL string WidgetURL string
LegalMentionsEntity string
LegalMentionsAddress string
LegalMentionsFullAddress []string
LegalMentionsEmail string
} }
var ( var (