mirror of
				https://gitlab.crans.org/nounous/ghostream.git
				synced 2025-10-31 22:34:30 +01:00 
			
		
		
		
	Compare commits
	
		
			37 Commits
		
	
	
		
			multi-qual
			...
			1520e78bad
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 1520e78bad | ||
|  | e47aefd6df | ||
|  | 7e0ee7aba5 | ||
|  | 8d2adad509 | ||
|  | 0035c63c22 | ||
|  | 849196b4cb | ||
|  | 205c4b526c | ||
|  | 1d117ea480 | ||
|  | 45cb61e436 | ||
|  | 7e4adb475a | ||
|  | d1c4f81f4e | ||
|  | b2104a0cb7 | ||
|  | 6ca354f44f | ||
|  | a20c6202fd | ||
|  | b52f377b6b | ||
|  | 3d8ba0623d | ||
|  | cfcde6f530 | ||
|  | 28ef6a5526 | ||
|  | 5ad8a69c4c | ||
|  | d334556d2b | ||
|  | 9625cba5e1 | ||
|  | e74acf04f7 | ||
|  | 2085d13c0d | ||
|  | 85a5606291 | ||
|  | 33f86a0742 | ||
|  | 11d89c6950 | ||
|  | c9a2d5b359 | ||
|  | ee927c5b8f | ||
|  | 955364a5fc | ||
|  | fc3c0f606c | ||
|  | e1bdae5380 | ||
|  | 26e7182085 | ||
|  | 59c47ca3e0 | ||
|  | 8eea7d6e4f | ||
|  | f394af9257 | ||
|  | 5b40aa886f | ||
|  | 9fc3d37e72 | 
| @@ -12,6 +12,6 @@ FROM alpine:3.12 | ||||
| RUN apk add --no-cache -X https://dl-cdn.alpinelinux.org/alpine/edge/community/ ffmpeg libsrt | ||||
| COPY --from=build_base /code/out/ghostream /app/ghostream | ||||
| WORKDIR /app | ||||
| # 2112 for monitoring, 8023 for Telnet, 8080 for Web, 9710 for SRT, 10000-10005 (UDP) for WebRTC | ||||
| EXPOSE 2112 8023 8080 9710/udp 10000-10005/udp | ||||
| # 2112 for monitoring, 8023 for Telnet, 8080 for Web, 9710 for SRT, 10000-11000 (UDP) for WebRTC | ||||
| EXPOSE 2112 8023 8080 9710/udp 10000-11000/udp | ||||
| CMD ["/app/ghostream"] | ||||
|   | ||||
| @@ -20,7 +20,7 @@ type Options struct { | ||||
|  | ||||
| // Backend to log user in | ||||
| type Backend interface { | ||||
| 	Login(string, string) (bool, error) | ||||
| 	Login(string, string) (bool, string, error) | ||||
| 	Close() | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -23,15 +23,15 @@ type Basic struct { | ||||
|  | ||||
| // Login hashs password and compare | ||||
| // 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] | ||||
| 	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)) | ||||
|  | ||||
| 	// Login succeeded if no error | ||||
| 	return err == nil, err | ||||
| 	return err == nil, username, err | ||||
| } | ||||
|  | ||||
| // Close has no connection to close | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import ( | ||||
|  | ||||
| func TestBasicLogin(t *testing.T) { | ||||
| 	basicCredentials := make(map[string]string) | ||||
| 	basicCredentials["demo"] = "$2b$15$LRnG3eIHFlYIguTxZOLH7eHwbQC/vqjnLq6nDFiHSUDKIU.f5/1H6" | ||||
| 	basicCredentials["demo"] = "$2b$10$xuU7XFwmRX2CMgdSaA8rM.4Y8.BtRNzhUedwN0G8tCegDRNUERTCS" | ||||
|  | ||||
| 	// Test good credentials | ||||
| 	backend, _ := New(&Options{Credentials: basicCredentials}) | ||||
|   | ||||
| @@ -3,12 +3,15 @@ package ldap | ||||
|  | ||||
| import ( | ||||
| 	"github.com/go-ldap/ldap/v3" | ||||
| 	"log" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // Options holds package configuration | ||||
| type Options struct { | ||||
| 	URI    string | ||||
| 	UserDn string | ||||
| 	Aliases map[string]map[string]string | ||||
| 	URI     string | ||||
| 	UserDn  string | ||||
| } | ||||
|  | ||||
| // LDAP authentification backend | ||||
| @@ -19,13 +22,35 @@ type LDAP struct { | ||||
|  | ||||
| // Login tries to bind to LDAP | ||||
| // Returns (true, nil) if success | ||||
| func (a LDAP) Login(username string, password string) (bool, error) { | ||||
| 	// Try to bind as user | ||||
| 	bindDn := "cn=" + username + "," + a.Cfg.UserDn | ||||
| 	err := a.Conn.Bind(bindDn, password) | ||||
| func (a LDAP) Login(username string, password string) (bool, string, error) { | ||||
| 	aliasSplit := strings.SplitN(username, "__", 2) | ||||
| 	potentialUsernames := []string{username} | ||||
|  | ||||
| 	// Login succeeded if no error | ||||
| 	return err == nil, err | ||||
| 	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 | ||||
| 		bindDn := "cn=" + username + "," + a.Cfg.UserDn | ||||
| 		err = a.Conn.Bind(bindDn, password) | ||||
| 		if err == nil { | ||||
| 			// Login succeeded if no error | ||||
| 			return true, aliasSplit[0], nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Unable to log in | ||||
| 	return err == nil, "", err | ||||
| } | ||||
|  | ||||
| // Close LDAP connection | ||||
|   | ||||
							
								
								
									
										118
									
								
								docs/Server-docker.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								docs/Server-docker.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" ?> | ||||
|  | ||||
| <Server version="7"> | ||||
|     <Name>OvenMediaEngine</Name> | ||||
|     <Type>origin</Type> | ||||
|     <IP>*</IP> | ||||
|  | ||||
|     <Bind> | ||||
|         <Providers> | ||||
|             <RTMP> | ||||
|                 <Port>1915</Port> | ||||
|             </RTMP> | ||||
|         </Providers> | ||||
|         <Publishers> | ||||
|             <WebRTC> | ||||
|                 <Signalling> | ||||
|                     <Port>3333</Port> | ||||
|                 </Signalling> | ||||
|                 <IceCandidates> | ||||
|                     <IceCandidate>*:10006-10010/udp</IceCandidate> | ||||
|                 </IceCandidates> | ||||
|             </WebRTC> | ||||
|             <HLS> | ||||
|               <Port>80</Port> | ||||
|             </HLS> | ||||
|             <DASH> | ||||
|                 <Port>80</Port> | ||||
|             </DASH> | ||||
|         </Publishers> | ||||
|     </Bind> | ||||
|  | ||||
|     <VirtualHosts> | ||||
|         <VirtualHost> | ||||
|             <Name>default</Name> | ||||
|             <Domain> | ||||
|                 <Names> | ||||
|                     <Name>*</Name> | ||||
|                 </Names> | ||||
|             </Domain> | ||||
|             <Applications> | ||||
|                 <Application> | ||||
|                     <Name>play</Name> | ||||
|                     <Type>live</Type> | ||||
|                     <Encodes> | ||||
|                         <Encode> | ||||
|                             <Name>opus_only</Name> | ||||
|                             <Audio> | ||||
|                                 <Codec>opus</Codec> | ||||
|                                 <Bitrate>128000</Bitrate> | ||||
|                                 <Samplerate>48000</Samplerate> | ||||
|                                 <Channel>2</Channel> | ||||
|                             </Audio> | ||||
|                             <Video> | ||||
|                                 <Bypass>true</Bypass> | ||||
|                             </Video> | ||||
|                         </Encode> | ||||
|                         <Encode> | ||||
|                             <Name>bypass</Name> | ||||
|                             <Audio> | ||||
|                                 <Bypass>true</Bypass> | ||||
|                             </Audio> | ||||
|                             <Video> | ||||
|                                 <Bypass>true</Bypass> | ||||
|                             </Video> | ||||
|                         </Encode> | ||||
|  | ||||
|                     </Encodes> | ||||
|                     <Streams> | ||||
|                         <Stream> | ||||
|                             <Name>${OriginStreamName}</Name> | ||||
|                             <Profiles> | ||||
|                                 <Profile>opus_only</Profile> | ||||
|                             </Profiles> | ||||
|                         </Stream> | ||||
|                         <Stream> | ||||
|                             <Name>${OriginStreamName}_bypass</Name> | ||||
|                             <Profiles> | ||||
|                                 <Profile>bypass</Profile> | ||||
|                             </Profiles> | ||||
|                         </Stream> | ||||
|  | ||||
|                     </Streams> | ||||
|                     <Providers> | ||||
|                         <RTMP> | ||||
|                             <BlockDuplicateStreamName>true</BlockDuplicateStreamName> | ||||
|                         </RTMP> | ||||
|                     </Providers> | ||||
|                     <Publishers> | ||||
|                         <ThreadCount>2</ThreadCount> | ||||
|                         <WebRTC> | ||||
|                             <Timeout>30000</Timeout> | ||||
|                         </WebRTC> | ||||
|                         <HLS> | ||||
|                             <SegmentDuration>2</SegmentDuration> | ||||
|                             <SegmentCount>2</SegmentCount> | ||||
|                             <CrossDomain> | ||||
|                                 <Url>*</Url> | ||||
|                             </CrossDomain> | ||||
|                         </HLS> | ||||
|                         <DASH> | ||||
|                             <SegmentDuration>2</SegmentDuration> | ||||
|                             <SegmentCount>2</SegmentCount> | ||||
|                             <CrossDomain> | ||||
|                                 <Url>*</Url> | ||||
|                             </CrossDomain> | ||||
|                         </DASH> | ||||
|                         <LLDASH> | ||||
|                             <SegmentDuration>2</SegmentDuration> | ||||
|                             <CrossDomain> | ||||
|                                 <Url>*</Url> | ||||
|                             </CrossDomain> | ||||
|                         </LLDASH> | ||||
|                     </Publishers> | ||||
|                 </Application> | ||||
|             </Applications> | ||||
|         </VirtualHost> | ||||
|     </VirtualHosts> | ||||
| </Server> | ||||
| @@ -26,11 +26,10 @@ services: | ||||
|       - "--certificatesResolvers.mytlschallenge.acme.httpChallenge.entryPoint=web" | ||||
|  | ||||
|   ghostream: | ||||
|     build: .. | ||||
|     build: https://gitlab.crans.org/nounous/ghostream.git | ||||
|     restart: always | ||||
|     ports: | ||||
|       - 9710:9710/udp | ||||
|       - 10000-10005:10000-10005/udp | ||||
|     volumes: | ||||
|       - ./ghostream_data:/etc/ghostream:ro | ||||
|     labels: | ||||
| @@ -40,3 +39,30 @@ services: | ||||
|       - "traefik.http.routers.ghostream.tls.certresolver=mytlschallenge" | ||||
|       - "traefik.http.routers.ghostream.service=ghostream" | ||||
|       - "traefik.http.services.ghostream.loadbalancer.server.port=8080" | ||||
|  | ||||
|   ovenmediaengine: | ||||
|     image: airensoft/ovenmediaengine:0.10.8 | ||||
|     restart: always | ||||
|     ports: | ||||
|       # WebRTC ICE | ||||
|       - 10006-10010:10006-10010/udp | ||||
|     volumes: | ||||
|       - ./ovenmediaengine_data/conf/Server-docker.xml:/opt/ovenmediaengine/bin/origin_conf/Server.xml:ro | ||||
|     labels: | ||||
|       - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https" | ||||
|  | ||||
|       - "traefik.http.routers.ovenmediaengine.rule=Host(`stream.example.com`) && PathPrefix(`/play/`)" | ||||
|       - "traefik.http.routers.ovenmediaengine.priority=101" | ||||
|       - "traefik.http.routers.ovenmediaengine.entrypoints=websecure" | ||||
|       - "traefik.http.routers.ovenmediaengine.tls.certresolver=mytlschallenge" | ||||
|       - "traefik.http.services.ovenmediaengine.loadbalancer.server.port=3333" | ||||
|       - "traefik.http.routers.ovenmediaengine.service=ovenmediaengine" | ||||
|       - "traefik.http.routers.ovenmediaengine.middlewares=sslheader" | ||||
|  | ||||
|       - "traefik.http.routers.ovenmediaengine-hls.rule=Host(`stream.example.com`) && Path(`/play/{app_name:.*}/{filename:.*}.{ext:(m3u8|mpd|ts)}`)" | ||||
|       - "traefik.http.routers.ovenmediaengine-hls.priority=102" | ||||
|       - "traefik.http.routers.ovenmediaengine-hls.entrypoints=websecure" | ||||
|       - "traefik.http.routers.ovenmediaengine-hls.tls.certresolver=mytlschallenge" | ||||
|       - "traefik.http.services.ovenmediaengine-hls.loadbalancer.server.port=80" | ||||
|       - "traefik.http.routers.ovenmediaengine-hls.service=ovenmediaengine-hls" | ||||
|       - "traefik.http.routers.ovenmediaengine-hls.middlewares=sslheader" | ||||
|   | ||||
| @@ -22,18 +22,25 @@ auth: | ||||
|  | ||||
|   # Basic backend configuration | ||||
|   # To generate bcrypt hashed password from Python, use: | ||||
|   # python3 -c 'import bcrypt; print(bcrypt.hashpw(b"PASSWORD", bcrypt.gensalt(rounds=15)).decode("ascii"))' | ||||
|   # python3 -c 'import bcrypt; print(bcrypt.hashpw(b"PASSWORD", bcrypt.gensalt(rounds=12)).decode("ascii"))' | ||||
|   # | ||||
|   #basic: | ||||
|   #  credentials: | ||||
|   #    # Demo user with password "demo" | ||||
|   #    demo: $2b$15$LRnG3eIHFlYIguTxZOLH7eHwbQC/vqjnLq6nDFiHSUDKIU.f5/1H6 | ||||
|   #    demo: $2b$10$xuU7XFwmRX2CMgdSaA8rM.4Y8.BtRNzhUedwN0G8tCegDRNUERTCS | ||||
|  | ||||
|   # LDAP backend configuration | ||||
|   # | ||||
|   #ldap: | ||||
|   #  uri: ldap://127.0.0.1:389 | ||||
|   #  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 ## | ||||
| # Forward an incoming stream to other servers | ||||
| @@ -61,6 +68,19 @@ monitoring: | ||||
|   # To limit access to only localhost, use 127.0.0.1:2112 | ||||
|   #listenAddress: :2112 | ||||
|  | ||||
| ## OvenMediaEngine ## | ||||
| # Send the stream data to OvenMediaEngine to handle properly the web client | ||||
| ome: | ||||
|   # If you disable OME module, the laggy webrtc client will be used. | ||||
|   # | ||||
|   #enabled: true | ||||
|   # | ||||
|   # The URL where OME listens RTMP, without the prefix. | ||||
|   #url: ovenmediaengine:1915 | ||||
|   # | ||||
|   # The OME app where OME is waiting for the data of Ghostream. | ||||
|   #app: play | ||||
|  | ||||
| ## SRT server ## | ||||
| # The SRT server receive incoming stream and can also serve video to clients. | ||||
| srt: | ||||
| @@ -160,17 +180,29 @@ web: | ||||
|   # | ||||
|   #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: | ||||
|   # If you disable webrtc module, the web client won't be able to play streams. | ||||
|   # | ||||
|   #enabled: true | ||||
|   #enabled: false | ||||
|  | ||||
|   # UDP port range used to stream | ||||
|   # This range must be opened in your firewall. | ||||
|   # | ||||
|   #minPortUDP: 10000 | ||||
|   #maxPortUDP: 10005 | ||||
|   #maxPortUDP: 11000 | ||||
|  | ||||
|   # STUN servers, you should host your own Coturn instance | ||||
|   # | ||||
|   | ||||
							
								
								
									
										9
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								go.mod
									
									
									
									
									
								
							| @@ -5,11 +5,12 @@ go 1.13 | ||||
| require ( | ||||
| 	github.com/go-ldap/ldap/v3 v3.2.3 | ||||
| 	github.com/gorilla/websocket v1.4.0 | ||||
| 	github.com/haivision/srtgo v0.0.0-20200731151239-e00427ae473a | ||||
| 	github.com/haivision/srtgo v0.0.0-20201025191851-67964e8f497a | ||||
| 	github.com/markbates/pkger v0.17.1 | ||||
| 	github.com/pion/rtp v1.6.0 | ||||
| 	github.com/pion/webrtc/v3 v3.0.0-beta.5 | ||||
| 	github.com/pion/rtp v1.6.1 | ||||
| 	github.com/pion/webrtc/v3 v3.0.0-beta.10 | ||||
| 	github.com/pkg/profile v1.5.0 | ||||
| 	github.com/prometheus/client_golang v1.7.1 | ||||
| 	github.com/sherifabdlnaby/configuro v0.0.2 | ||||
| 	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 | ||||
| 	golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a | ||||
| ) | ||||
|   | ||||
							
								
								
									
										47
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								go.sum
									
									
									
									
									
								
							| @@ -120,8 +120,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg | ||||
| github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= | ||||
| github.com/haivision/srtgo v0.0.0-20200731151239-e00427ae473a h1:JliMkv/mAqM5+QzG6Hkw1XcVl1crU8yIQGnhppMv7s0= | ||||
| github.com/haivision/srtgo v0.0.0-20200731151239-e00427ae473a/go.mod h1:yVZ4oACfcnUAcxrh+0b6IuIWfkHLK3IAQ99tuuhRx54= | ||||
| github.com/haivision/srtgo v0.0.0-20201025191851-67964e8f497a h1:54abJQezjMoiP+xMQ3ZQbcDXFjqytAYm/n0EVqrYeXg= | ||||
| github.com/haivision/srtgo v0.0.0-20201025191851-67964e8f497a/go.mod h1:7izzTiCO3zc9ZIVTFMjxUiYL+kgryFP9rl3bsweqdmc= | ||||
| github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= | ||||
| github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||||
| github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | ||||
| @@ -192,29 +192,30 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181 | ||||
| github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | ||||
| github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0= | ||||
| github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg= | ||||
| github.com/pion/dtls/v2 v2.0.2 h1:FHCHTiM182Y8e15aFTiORroiATUI16ryHiQh8AIOJ1E= | ||||
| github.com/pion/dtls/v2 v2.0.2/go.mod h1:27PEO3MDdaCfo21heT59/vsdmZc0zMt9wQPcSlLu/1I= | ||||
| github.com/pion/ice/v2 v2.0.6 h1:7Jf3AX6VIjgO2tGRyT0RGGxkDYOF4m5I5DQzf34IN1Y= | ||||
| github.com/pion/ice/v2 v2.0.6/go.mod h1:xOXvVRlQC/B7FPJeJYKY6IepFRAKb3t1un1K9boYaaQ= | ||||
| github.com/pion/dtls/v2 v2.0.3 h1:3qQ0s4+TXD00rsllL8g8KQcxAs+Y/Z6oz618RXX6p14= | ||||
| github.com/pion/dtls/v2 v2.0.3/go.mod h1:TUjyL8bf8LH95h81Xj7kATmzMRt29F/4lxpIPj2Xe4Y= | ||||
| github.com/pion/ice/v2 v2.0.9 h1:oHbiN6Q9tgb8Gfu3I4cbr5mHRE1uqiuFABQ8CbWjIyk= | ||||
| github.com/pion/ice/v2 v2.0.9/go.mod h1:NK+o39ynb+N1YSj9fPgWs3vjVcrsWw0KCr/311MqVq8= | ||||
| github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= | ||||
| github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= | ||||
| github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY= | ||||
| github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0= | ||||
| github.com/pion/quic v0.1.4 h1:bNz9sCJjlM3GqMdq7Fne57FiWfdyiJ++yHVbuqeoD3Y= | ||||
| github.com/pion/quic v0.1.4/go.mod h1:dBhNvkLoQqRwfi6h3Vqj3IcPLgiW7rkZxBbRdp7Vzvk= | ||||
| github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= | ||||
| github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= | ||||
| github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= | ||||
| github.com/pion/rtcp v1.2.3 h1:2wrhKnqgSz91Q5nzYTO07mQXztYPtxL8a0XOss4rJqA= | ||||
| github.com/pion/rtcp v1.2.3/go.mod h1:zGhIv0RPRF0Z1Wiij22pUt5W/c9fevqSzT4jje/oK7I= | ||||
| github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk= | ||||
| github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI= | ||||
| github.com/pion/rtcp v1.2.4 h1:NT3H5LkUGgaEapvp0HGik+a+CpflRF7KTD7H+o7OWIM= | ||||
| github.com/pion/rtcp v1.2.4/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= | ||||
| github.com/pion/rtp v1.6.1 h1:2Y2elcVBrahYnHKN2X7rMHX/r1R4TEBMP1LaVu/wNhk= | ||||
| github.com/pion/rtp v1.6.1/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= | ||||
| github.com/pion/sctp v1.7.10 h1:o3p3/hZB5Cx12RMGyWmItevJtZ6o2cpuxaw6GOS4x+8= | ||||
| github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= | ||||
| github.com/pion/sdp/v3 v3.0.1 h1:we4OyeTT6s+6sxrAKy/LVywskx1dzUQlh4xu3b+iAs0= | ||||
| github.com/pion/sdp/v3 v3.0.1/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk= | ||||
| github.com/pion/srtp v1.5.1 h1:9Q3jAfslYZBt+C69SI/ZcONJh9049JUHZWYRRf5KEKw= | ||||
| github.com/pion/srtp v1.5.1/go.mod h1:B+QgX5xPeQTNc1CJStJPHzOlHK66ViMDWTT0HZTCkcA= | ||||
| github.com/pion/sctp v1.7.11 h1:UCnj7MsobLKLuP/Hh+JMiI/6W5Bs/VF45lWKgHFjSIE= | ||||
| github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= | ||||
| github.com/pion/sdp/v3 v3.0.2 h1:UNnSPVaMM+Pdu/mR9UvAyyo6zkdYbKeuOooCwZvTl/g= | ||||
| github.com/pion/sdp/v3 v3.0.2/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk= | ||||
| github.com/pion/srtp v1.5.2 h1:25DmvH+fqKZDqvX64vTwnycVwL9ooJxHF/gkX16bDBY= | ||||
| github.com/pion/srtp v1.5.2/go.mod h1:NiBff/MSxUwMUwx/fRNyD/xGE+dVvf8BOCeXhjCXZ9U= | ||||
| github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= | ||||
| github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= | ||||
| github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= | ||||
| @@ -225,12 +226,14 @@ github.com/pion/turn/v2 v2.0.4 h1:oDguhEv2L/4rxwbL9clGLgtzQPjtuZwCdoM7Te8vQVk= | ||||
| github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog= | ||||
| github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI= | ||||
| github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths= | ||||
| github.com/pion/webrtc/v3 v3.0.0-beta.5 h1:A1hApeo77Kp2sHWwd53rIHZZm5W9JXEn//5jHj9+fdc= | ||||
| github.com/pion/webrtc/v3 v3.0.0-beta.5/go.mod h1:HjQaNPq5SdNazGlwiim6/lWBkZD97mPPyDttr7byNA4= | ||||
| github.com/pion/webrtc/v3 v3.0.0-beta.10 h1:1aBn9jv/oe4v2Uf47HutWIjg2i2ZP/O7HqpgKPqSuhE= | ||||
| github.com/pion/webrtc/v3 v3.0.0-beta.10/go.mod h1:GlriYYHJ5KkNsCunm3oFDPql4TDTrrNoI9iSWWSnafA= | ||||
| github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug= | ||||
| github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= | ||||
| @@ -349,6 +352,8 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf | ||||
| golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= | ||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= | ||||
| golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
| @@ -384,6 +389,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R | ||||
| golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||
| golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= | ||||
| golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | ||||
| golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c h1:dk0ukUIHmGHqASjP0iue2261isepFCC6XRCSd1nHgDw= | ||||
| golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c/go.mod h1:iQL9McJNjoIa5mjH6nYTCTZXUN6RP+XW3eib7Ya3XcI= | ||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| @@ -416,6 +423,10 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w | ||||
| golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= | ||||
| golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c h1:38q6VNPWR010vN82/SB121GujZNIfAUb4YttE2rhGuc= | ||||
| golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= | ||||
| golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| @@ -445,6 +456,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= | ||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= | ||||
| google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= | ||||
| google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"gitlab.crans.org/nounous/ghostream/stream/ovenmediaengine" | ||||
| 	"net" | ||||
|  | ||||
| 	"github.com/sherifabdlnaby/configuro" | ||||
| @@ -23,6 +24,7 @@ type Config struct { | ||||
| 	Auth       auth.Options | ||||
| 	Forwarding forwarding.Options | ||||
| 	Monitoring monitoring.Options | ||||
| 	OME        ovenmediaengine.Options | ||||
| 	Srt        srt.Options | ||||
| 	Telnet     telnet.Options | ||||
| 	Transcoder transcoder.Options | ||||
| @@ -40,8 +42,9 @@ func New() *Config { | ||||
| 				Credentials: make(map[string]string), | ||||
| 			}, | ||||
| 			LDAP: ldap.Options{ | ||||
| 				URI:    "ldap://127.0.0.1:389", | ||||
| 				UserDn: "cn=users,dc=example,dc=com", | ||||
| 				Aliases: make(map[string]map[string]string), | ||||
| 				URI:     "ldap://127.0.0.1:389", | ||||
| 				UserDn:  "cn=users,dc=example,dc=com", | ||||
| 			}, | ||||
| 		}, | ||||
| 		Forwarding: make(map[string][]string), | ||||
| @@ -49,6 +52,11 @@ func New() *Config { | ||||
| 			Enabled:       true, | ||||
| 			ListenAddress: ":2112", | ||||
| 		}, | ||||
| 		OME: ovenmediaengine.Options{ | ||||
| 			Enabled: true, | ||||
| 			URL:     "ovenmediaengine:1915", | ||||
| 			App:     "play", | ||||
| 		}, | ||||
| 		Srt: srt.Options{ | ||||
| 			Enabled:       true, | ||||
| 			ListenAddress: ":9710", | ||||
| @@ -75,10 +83,15 @@ func New() *Config { | ||||
| 			MapDomainToStream:           make(map[string]string), | ||||
| 			PlayerPoster:                "/static/img/no_stream.svg", | ||||
| 			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{ | ||||
| 			Enabled:     true, | ||||
| 			MaxPortUDP:  10005, | ||||
| 			Enabled:     false, | ||||
| 			MaxPortUDP:  11000, | ||||
| 			MinPortUDP:  10000, | ||||
| 			STUNServers: []string{"stun:stun.l.google.com:19302"}, | ||||
| 		}, | ||||
| @@ -117,7 +130,7 @@ func Load() (*Config, error) { | ||||
|  | ||||
| 	// If no credentials register, add demo account with password "demo" | ||||
| 	if len(cfg.Auth.Basic.Credentials) < 1 { | ||||
| 		cfg.Auth.Basic.Credentials["demo"] = "$2b$15$LRnG3eIHFlYIguTxZOLH7eHwbQC/vqjnLq6nDFiHSUDKIU.f5/1H6" | ||||
| 		cfg.Auth.Basic.Credentials["demo"] = "$2b$10$xuU7XFwmRX2CMgdSaA8rM.4Y8.BtRNzhUedwN0G8tCegDRNUERTCS" | ||||
| 	} | ||||
|  | ||||
| 	return cfg, nil | ||||
|   | ||||
							
								
								
									
										8
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								main.go
									
									
									
									
									
								
							| @@ -5,8 +5,10 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"gitlab.crans.org/nounous/ghostream/stream/ovenmediaengine" | ||||
| 	"log" | ||||
|  | ||||
| 	"github.com/pkg/profile" | ||||
| 	"gitlab.crans.org/nounous/ghostream/auth" | ||||
| 	"gitlab.crans.org/nounous/ghostream/internal/config" | ||||
| 	"gitlab.crans.org/nounous/ghostream/internal/monitoring" | ||||
| @@ -20,6 +22,9 @@ import ( | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	// TODO Don't always profile if not needed | ||||
| 	defer profile.Start().Stop() | ||||
|  | ||||
| 	// Configure logger | ||||
| 	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) | ||||
|  | ||||
| @@ -45,9 +50,10 @@ func main() { | ||||
| 	go transcoder.Init(streams, &cfg.Transcoder) | ||||
| 	go forwarding.Serve(streams, cfg.Forwarding) | ||||
| 	go monitoring.Serve(&cfg.Monitoring) | ||||
| 	go ovenmediaengine.Serve(streams, &cfg.OME) | ||||
| 	go srt.Serve(streams, authBackend, &cfg.Srt) | ||||
| 	go telnet.Serve(streams, &cfg.Telnet) | ||||
| 	go web.Serve(streams, &cfg.Web) | ||||
| 	go web.Serve(streams, &cfg.Web, &cfg.OME) | ||||
| 	go webrtc.Serve(streams, &cfg.WebRTC) | ||||
|  | ||||
| 	// Wait for routines | ||||
|   | ||||
| @@ -40,6 +40,7 @@ func Serve(streams *messaging.Streams, cfg Options) { | ||||
| 		stream, err := streams.Get(name) | ||||
| 		if err != nil { | ||||
| 			log.Printf("Failed to get stream '%s'", name) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// Get specific quality | ||||
| @@ -74,8 +75,8 @@ func forward(streamName string, q *messaging.Quality, fwdCfg []string) { | ||||
| 		formattedURL = strings.ReplaceAll(formattedURL, "%S", fmt.Sprintf("%02d", now.Second())) | ||||
| 		formattedURL = strings.ReplaceAll(formattedURL, "%name", streamName) | ||||
|  | ||||
| 		params = append(params, "-f", "flv", "-preset", "ultrafast", "-tune", "zerolatency", | ||||
| 			"-c", "copy", formattedURL) | ||||
| 		params = append(params, "-f", "flv", | ||||
| 			"-c:v", "copy", "-c:a", "aac", "-b:a", "160k", "-ar", "44100", formattedURL) | ||||
| 	} | ||||
| 	ffmpeg := exec.Command("ffmpeg", params...) | ||||
|  | ||||
|   | ||||
							
								
								
									
										112
									
								
								stream/ovenmediaengine/ovenmediaengine.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								stream/ovenmediaengine/ovenmediaengine.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| // Package ovenmediaengine provides the forwarding to an ovenmediaengine server to handle the web client | ||||
| package ovenmediaengine | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"os/exec" | ||||
|  | ||||
| 	"gitlab.crans.org/nounous/ghostream/messaging" | ||||
| ) | ||||
|  | ||||
| // Options holds ovenmediaengine package configuration | ||||
| type Options struct { | ||||
| 	Enabled bool | ||||
| 	URL     string | ||||
| 	App     string | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	cfg *Options | ||||
| ) | ||||
|  | ||||
| // Serve handles incoming packets from SRT and forward them to OME | ||||
| func Serve(streams *messaging.Streams, c *Options) { | ||||
| 	cfg = c | ||||
|  | ||||
| 	if !c.Enabled { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Subscribe to new stream event | ||||
| 	event := make(chan string, 8) | ||||
| 	streams.Subscribe(event) | ||||
| 	log.Printf("Stream forwarding to OME initialized") | ||||
|  | ||||
| 	// For each new stream | ||||
| 	for name := range event { | ||||
| 		// Get stream | ||||
| 		stream, err := streams.Get(name) | ||||
| 		if err != nil { | ||||
| 			log.Printf("Failed to get stream '%s'", name) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		qualityName := "source" | ||||
| 		quality, err := stream.GetQuality(qualityName) | ||||
| 		if err != nil { | ||||
| 			log.Printf("Failed to get quality '%s'", qualityName) | ||||
| 		} | ||||
|  | ||||
| 		// Start forwarding | ||||
| 		log.Printf("Starting forwarding to OME for '%s'", name) | ||||
| 		go forward(name, quality) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Start a FFMPEG instance and redirect stream output to OME | ||||
| func forward(name string, q *messaging.Quality) { | ||||
| 	output := make(chan []byte, 1024) | ||||
| 	q.Register(output) | ||||
|  | ||||
| 	// TODO When a new OME version got released with SRT support, directly forward SRT packets, without using unwanted RTMP transport | ||||
| 	// Launch FFMPEG instance | ||||
| 	params := []string{"-hide_banner", "-loglevel", "error", "-i", "pipe:0", "-f", "flv", "-c:v", "copy", | ||||
| 		"-c:a", "aac", "-b:a", "160k", "-ar", "44100", | ||||
| 		fmt.Sprintf("rtmp://%s/%s/%s", cfg.URL, cfg.App, name)} | ||||
| 	ffmpeg := exec.Command("ffmpeg", params...) | ||||
|  | ||||
| 	// Open pipes | ||||
| 	input, err := ffmpeg.StdinPipe() | ||||
| 	if err != nil { | ||||
| 		log.Printf("Error while opening forwarding ffmpeg input pipe: %s", err) | ||||
| 		return | ||||
| 	} | ||||
| 	errOutput, err := ffmpeg.StderrPipe() | ||||
| 	if err != nil { | ||||
| 		log.Printf("Error while opening forwarding ffmpeg output pipe: %s", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Start FFMpeg | ||||
| 	if err := ffmpeg.Start(); err != nil { | ||||
| 		log.Printf("Error while starting forwarding ffmpeg instance: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Kill FFMPEG when stream is ended | ||||
| 	defer func() { | ||||
| 		_ = input.Close() | ||||
| 		_ = errOutput.Close() | ||||
| 		_ = ffmpeg.Process.Kill() | ||||
| 		q.Unregister(output) | ||||
| 	}() | ||||
|  | ||||
| 	// Log standard error output | ||||
| 	go func() { | ||||
| 		scanner := bufio.NewScanner(errOutput) | ||||
| 		for scanner.Scan() { | ||||
| 			log.Printf("[FORWARDING OME FFMPEG %s] %s", name, scanner.Text()) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	// Read stream output and redirect immediately to ffmpeg | ||||
| 	for data := range output { | ||||
| 		_, err := input.Write(data) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			log.Printf("Error while writing to forwarded stream: %s", err) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,9 +1,6 @@ | ||||
| // Package srt serves a SRT server | ||||
| package srt | ||||
|  | ||||
| // #include <srt/srt.h> | ||||
| import "C" | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net" | ||||
| @@ -62,7 +59,7 @@ func Serve(streams *messaging.Streams, authBackend auth.Backend, cfg *Options) { | ||||
|  | ||||
| 	for { | ||||
| 		// Wait for new connection | ||||
| 		s, err := sck.Accept() | ||||
| 		s, _, err := sck.Accept() | ||||
| 		if err != nil { | ||||
| 			// Something wrong happened | ||||
| 			log.Println(err) | ||||
| @@ -73,7 +70,7 @@ func Serve(streams *messaging.Streams, authBackend auth.Backend, cfg *Options) { | ||||
| 		// Without this, the SRT buffer might get full before reading it | ||||
|  | ||||
| 		// streamid can be "name:password" for streamer or "name" for viewer | ||||
| 		streamID, err := s.GetSockOptString(C.SRTO_STREAMID) | ||||
| 		streamID, err := s.GetSockOptString(srtgo.SRTO_STREAMID) | ||||
| 		if err != nil { | ||||
| 			log.Print("Failed to get socket streamid") | ||||
| 			continue | ||||
| @@ -85,7 +82,9 @@ func Serve(streams *messaging.Streams, authBackend auth.Backend, cfg *Options) { | ||||
| 			name, password := split[0], split[1] | ||||
| 			if authBackend != nil { | ||||
| 				// check password | ||||
| 				if ok, err := authBackend.Login(name, password); !ok || err != nil { | ||||
| 				ok, username, err := authBackend.Login(name, password) | ||||
| 				name = username | ||||
| 				if ok || err != nil { | ||||
| 					log.Printf("Failed to authenticate for stream %s", name) | ||||
| 					s.Close() | ||||
| 					continue | ||||
|   | ||||
| @@ -3,7 +3,9 @@ package webrtc | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"math/rand" | ||||
| 	"net" | ||||
| 	"os/exec" | ||||
|  | ||||
| @@ -17,20 +19,23 @@ func ingest(name string, q *messaging.Quality) { | ||||
| 	videoInput := make(chan []byte, 1024) | ||||
| 	q.Register(videoInput) | ||||
|  | ||||
| 	// Open a UDP Listener for RTP Packets on port 5004 | ||||
| 	audioListener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5004}) | ||||
| 	// FIXME Mux into RTP without having multiple UDP listeners | ||||
| 	firstPort := int(rand.Int31n(63535)) + 2000 | ||||
|  | ||||
| 	// Open UDP listeners for RTP Packets | ||||
| 	audioListener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: firstPort}) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Faited to open UDP listener %s", err) | ||||
| 		return | ||||
| 	} | ||||
| 	videoListener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5005}) | ||||
| 	videoListener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: firstPort + 1}) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Faited to open UDP listener %s", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Start ffmpag to convert videoInput to video and audio UDP | ||||
| 	ffmpeg, err := startFFmpeg(videoInput) | ||||
| 	ffmpeg, err := startFFmpeg(videoInput, firstPort) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Error while starting ffmpeg: %s", err) | ||||
| 		return | ||||
| @@ -115,14 +120,14 @@ func ingest(name string, q *messaging.Quality) { | ||||
| 	q.Unregister(videoInput) | ||||
| } | ||||
|  | ||||
| func startFFmpeg(in <-chan []byte) (ffmpeg *exec.Cmd, err error) { | ||||
| func startFFmpeg(in <-chan []byte, listeningPort int) (ffmpeg *exec.Cmd, err error) { | ||||
| 	ffmpegArgs := []string{"-hide_banner", "-loglevel", "error", "-i", "pipe:0", | ||||
| 		// Audio | ||||
| 		"-vn", "-c:a", "libopus", "-b:a", "160k", | ||||
| 		"-f", "rtp", "rtp://127.0.0.1:5004", | ||||
| 		"-vn", "-c:a", "libopus", "-b:a", "96k", | ||||
| 		"-f", "rtp", fmt.Sprintf("rtp://127.0.0.1:%d", listeningPort), | ||||
| 		// Source | ||||
| 		"-an", "-c:v", "copy", "-b:v", "3000k", "-maxrate", "5000k", "-bufsize", "5000k", | ||||
| 		"-f", "rtp", "rtp://127.0.0.1:5005"} | ||||
| 		"-an", "-c:v", "copy", | ||||
| 		"-f", "rtp", fmt.Sprintf("rtp://127.0.0.1:%d", listeningPort+1)} | ||||
| 	ffmpeg = exec.Command("ffmpeg", ffmpegArgs...) | ||||
|  | ||||
| 	// Handle errors output | ||||
|   | ||||
| @@ -10,15 +10,21 @@ import ( | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/markbates/pkger" | ||||
| 	"gitlab.crans.org/nounous/ghostream/internal/monitoring" | ||||
| 	"gitlab.crans.org/nounous/ghostream/stream/ovenmediaengine" | ||||
| 	"gitlab.crans.org/nounous/ghostream/stream/webrtc" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// Precompile regex | ||||
| 	validPath = regexp.MustCompile("^/[a-z0-9@_-]*$") | ||||
|  | ||||
| 	counterMutex     = new(sync.Mutex) | ||||
| 	connectedClients = make(map[string]map[string]int64) | ||||
| ) | ||||
|  | ||||
| // Handle site index and viewer pages | ||||
| @@ -61,7 +67,8 @@ func viewerHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 		Cfg       *Options | ||||
| 		Path      string | ||||
| 		WidgetURL string | ||||
| 	}{Path: path, Cfg: cfg, WidgetURL: ""} | ||||
| 		OMECfg    *ovenmediaengine.Options | ||||
| 	}{Path: path, Cfg: cfg, WidgetURL: "", OMECfg: omeCfg} | ||||
|  | ||||
| 	// Load widget is user does not disable it with ?nowidget | ||||
| 	if _, ok := r.URL.Query()["nowidget"]; !ok { | ||||
| @@ -88,14 +95,43 @@ func staticHandler() http.Handler { | ||||
| } | ||||
|  | ||||
| func statisticsHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	// Retrieve stream name from URL | ||||
| 	name := strings.SplitN(strings.Replace(r.URL.Path[7:], "/", "", -1), "@", 2)[0] | ||||
| 	userCount := 0 | ||||
|  | ||||
| 	// Clients have a unique generated identifier per session, that expires in 40 seconds. | ||||
| 	// Each time the client connects to this page, the identifier is renewed. | ||||
| 	// Yeah, that's not a good way to have stats, but it works... | ||||
| 	if connectedClients[name] == nil { | ||||
| 		counterMutex.Lock() | ||||
| 		connectedClients[name] = make(map[string]int64) | ||||
| 		counterMutex.Unlock() | ||||
| 	} | ||||
| 	currentTime := time.Now().Unix() | ||||
| 	if _, ok := r.URL.Query()["uid"]; ok { | ||||
| 		uid := r.URL.Query()["uid"][0] | ||||
| 		counterMutex.Lock() | ||||
| 		connectedClients[name][uid] = currentTime | ||||
| 		counterMutex.Unlock() | ||||
| 	} | ||||
| 	toDelete := make([]string, 0) | ||||
| 	counterMutex.Lock() | ||||
| 	for uid, oldTime := range connectedClients[name] { | ||||
| 		if currentTime-oldTime > 40 { | ||||
| 			toDelete = append(toDelete, uid) | ||||
| 		} | ||||
| 	} | ||||
| 	for _, uid := range toDelete { | ||||
| 		delete(connectedClients[name], uid) | ||||
| 	} | ||||
| 	counterMutex.Unlock() | ||||
|  | ||||
| 	// Get requested stream | ||||
| 	stream, err := streams.Get(name) | ||||
| 	if err == nil { | ||||
| 		userCount = stream.ClientCount() | ||||
| 		userCount += webrtc.GetNumberConnectedSessions(name) | ||||
| 		userCount += len(connectedClients[name]) | ||||
| 	} | ||||
|  | ||||
| 	// Display connected users statistics | ||||
|   | ||||
| @@ -9,6 +9,7 @@ export class ViewerCounter { | ||||
|     constructor(element, streamName) { | ||||
|         this.element = element; | ||||
|         this.url = "/_stats/" + streamName; | ||||
|         this.uid = Math.floor(1e19 * Math.random()).toString(16); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -21,7 +22,7 @@ export class ViewerCounter { | ||||
|     } | ||||
|  | ||||
|     refreshViewersCounter() { | ||||
|         fetch(this.url) | ||||
|         fetch(this.url + "?uid=" + this.uid) | ||||
|             .then(response => response.json()) | ||||
|             .then((data) => this.element.innerText = data.ConnectedViewers) | ||||
|             .catch(console.log); | ||||
|   | ||||
							
								
								
									
										116
									
								
								web/static/js/ovenplayer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								web/static/js/ovenplayer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| import { ViewerCounter } from "./modules/viewerCounter.js"; | ||||
|  | ||||
| /** | ||||
|  * Initialize viewer page | ||||
|  * | ||||
|  * @param {String} stream | ||||
|  * @param {String} omeApp | ||||
|  * @param {Number} viewersCounterRefreshPeriod | ||||
|  * @param {String} posterUrl | ||||
|  */ | ||||
| export function initViewerPage(stream, omeApp, viewersCounterRefreshPeriod, posterUrl) { | ||||
|     // Create viewer counter | ||||
|     const viewerCounter = new ViewerCounter( | ||||
|         document.getElementById("connected-people"), | ||||
|         stream, | ||||
|     ); | ||||
|     viewerCounter.regularUpdate(viewersCounterRefreshPeriod); | ||||
|     viewerCounter.refreshViewersCounter(); | ||||
|  | ||||
|     // Side widget toggler | ||||
|     const sideWidgetToggle = document.getElementById("sideWidgetToggle"); | ||||
|     const sideWidget = document.getElementById("sideWidget"); | ||||
|     if (sideWidgetToggle !== null && sideWidget !== null) { | ||||
|         // On click, toggle side widget visibility | ||||
|         sideWidgetToggle.addEventListener("click", function () { | ||||
|             if (sideWidget.style.display === "none") { | ||||
|                 sideWidget.style.display = "block"; | ||||
|                 sideWidgetToggle.textContent = "»"; | ||||
|             } else { | ||||
|                 sideWidget.style.display = "none"; | ||||
|                 sideWidgetToggle.textContent = "«"; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     // Create player | ||||
|     let player = OvenPlayer.create("viewer", { | ||||
|         title: stream, | ||||
|         image: posterUrl, | ||||
|         autoStart: true, | ||||
|         mute: true, | ||||
|         expandFullScreenUI: true, | ||||
|         sources: [ | ||||
|             { | ||||
|                 "file": "wss://" + window.location.host + "/" + omeApp + "/" + stream, | ||||
|                 "type": "webrtc", | ||||
|                 "label": " WebRTC - Source" | ||||
|             }, | ||||
|             { | ||||
|                 "file": "https://" + window.location.host + "/" + omeApp + "/" + stream + "_bypass/playlist.m3u8", | ||||
|                 "type": "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) { | ||||
|         if (data.newstate === "loading") { | ||||
|             document.getElementById("connectionIndicator").style.fill = '#ffc107' | ||||
|         } | ||||
|         if (data.newstate === "playing") { | ||||
|             document.getElementById("connectionIndicator").style.fill = '#28a745' | ||||
|         } | ||||
|         if (data.newstate === "idle") { | ||||
|             document.getElementById("connectionIndicator").style.fill = '#dc3545' | ||||
|         } | ||||
|     }) | ||||
|     player.on("error", function (error) { | ||||
|         document.getElementById("connectionIndicator").style.fill = '#dc3545' | ||||
|         if (error.code === 501 || error.code === 406) { | ||||
|             // Clear messages | ||||
|             const errorMsg = document.getElementsByClassName("op-message-text")[0] | ||||
|             errorMsg.textContent = "" | ||||
|  | ||||
|             const warningIcon = document.getElementsByClassName("op-message-icon")[0] | ||||
|             warningIcon.textContent = "" | ||||
|  | ||||
|             // Reload in 30s | ||||
|             setTimeout(function () { | ||||
|                 player.load() | ||||
|             }, 30000) | ||||
|         } else { | ||||
|             console.log(error); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     // Register keyboard events | ||||
|     window.addEventListener("keydown", (event) => { | ||||
|         switch (event.key) { | ||||
|             case "f": | ||||
|                 // F key put player in fullscreen | ||||
|                 if (document.fullscreenElement !== null) { | ||||
|                     document.exitFullscreen() | ||||
|                 } else { | ||||
|                     document.getElementsByTagName("video")[0].requestFullscreen() | ||||
|                 } | ||||
|                 break; | ||||
|             case "m": | ||||
|             case " ": | ||||
|                 // M and space key mute player | ||||
|                 player.setMute(!player.getMute()) | ||||
|                 event.preventDefault() | ||||
|                 player.play() | ||||
|                 break; | ||||
|         } | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										2
									
								
								web/static/ovenplayer/ovenplayer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								web/static/ovenplayer/ovenplayer.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								web/static/ovenplayer/ovenplayer.js.LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/static/ovenplayer/ovenplayer.js.LICENSE
									
									
									
									
									
										Normal 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
											
										
									
								
							| @@ -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
											
										
									
								
							| @@ -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
											
										
									
								
							| @@ -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 */ | ||||
							
								
								
									
										2
									
								
								web/static/ovenplayer/ovenplayer.provider.Html5-0.9.0.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								web/static/ovenplayer/ovenplayer.provider.Html5-0.9.0.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -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
											
										
									
								
							| @@ -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 */ | ||||
							
								
								
									
										2
									
								
								web/static/ovenplayer/ovenplayer.sdk.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								web/static/ovenplayer/ovenplayer.sdk.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								web/static/ovenplayer/ovenplayer.sdk.js.LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/static/ovenplayer/ovenplayer.sdk.js.LICENSE
									
									
									
									
									
										Normal 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 */ | ||||
| @@ -9,7 +9,11 @@ | ||||
|   </p> | ||||
|  | ||||
|   <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> | ||||
|   <p> | ||||
| @@ -21,11 +25,7 @@ | ||||
|   <ul> | ||||
|     <li> | ||||
|       <b>Serveur :</b> | ||||
|       <code>srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}</code>, | ||||
|     </li> | ||||
|     <li> | ||||
|       <b>Clé de stream :</b> | ||||
|       <code>IDENTIFIANT:MOT_DE_PASSE</code> | ||||
|       <code>srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?IDENTIFIANT:MOT_DE_PASSE</code>, | ||||
|       avec <code>IDENTIFIANT</code> et <code>MOT_DE_PASSE</code> | ||||
|       vos identifiants. | ||||
|     </li> | ||||
| @@ -46,29 +46,104 @@ | ||||
|   <p> | ||||
|     <code> | ||||
|       {{/* 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 | ||||
|       srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid=IDENTIFIANT:MOT_DE_PASSE | ||||
|     </code> | ||||
|   </p> | ||||
|  | ||||
|   <h2>Comment lire un flux depuis un lecteur externe ?</h2> | ||||
|   <p> | ||||
|     À l'heure actuelle, la plupart des lecteurs vidéos ne supportent | ||||
|     pas le protocole SRT, ou le supportent mal. Un travail est en | ||||
|     cours pour les rendre un maximum compatibles. Liste non exhaustive | ||||
|     des lecteurs vidéos testés : | ||||
|   </p> | ||||
|  | ||||
|   <h3>FFPlay</h3> | ||||
|   <p> | ||||
|     Si FFMpeg est installé sur votre machine, il est accompagné d'un | ||||
|     lecteur vidéo nommé <code>ffplay</code>. Si FFMpeg est compilé | ||||
|     avec le support de SRT (c'est le cas sur la plupart des distributions, | ||||
|     sauf cas ci-dessous), il vous suffira d'exécuter : | ||||
|   </p> | ||||
|  | ||||
|   <p> | ||||
|     <code> | ||||
|       ffplay -fflags nobuffer srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid=IDENTIFIANT | ||||
|     </code> | ||||
|   </p> | ||||
|  | ||||
|   <h3>MPV</h3> | ||||
|  | ||||
|   <p> | ||||
|     MPV supporte officiellement SRT depuis le 16 octobre 2020. | ||||
|     Néanmoins, la version stable de MPV est beaucoup plus vieille. | ||||
|     Vous devez alors utiliser une version de développement pour | ||||
|     pouvoir lire un flux SRT depuis MPV. L'installation se fait | ||||
|     depuis <a href="https://mpv.io/installation/"> cette page</a>. | ||||
|     Sous Arch Linux, il vous suffit de récupérer le paquet | ||||
|     <code>mpv-git</code> dans l'AUR. Pour lire le flux, il suffit | ||||
|     d'exécuter : | ||||
|   </p> | ||||
|  | ||||
|   <p> | ||||
|     <code> | ||||
|       mpv srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid=IDENTIFIANT | ||||
|     </code> | ||||
|   </p> | ||||
|  | ||||
|   <h3>VLC Media Player</h3> | ||||
|  | ||||
|   <p> | ||||
|     Bien que VLC supporte officiellement le protocole SRT, | ||||
|     toutes les options ne sont pas encore implémentées, | ||||
|     notamment l'option pour choisir son stream. | ||||
|     Cette option n'est supportée que dans la version de développement | ||||
|     depuis très récemment, grâce à un patch de l'un des développeurs | ||||
|     de Ghostream.  Sous Arch Linux, il suffit de récupérer | ||||
|     le paquet <code>vlc-git</code> de l'AUR. Avec un VLC à jour, | ||||
|     il suffit d'exécuter : | ||||
|   </p> | ||||
|  | ||||
|   <p> | ||||
|     <code> | ||||
|       vlc srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid=IDENTIFIANT | ||||
|     </code> | ||||
|   </p> | ||||
|  | ||||
|   <p> | ||||
|     Ou bien d'aller dans Média -> Ouvrir un flux réseau et d'entrer l'URL | ||||
|     <code>srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid=IDENTIFIANT</code>. | ||||
|   </p> | ||||
|  | ||||
|   <h3>Le protocole n'existe pas ou n'est pas supporté.</h3> | ||||
|   <p> | ||||
|     La technologie SRT est très récente et n'est pas supportée par | ||||
|     les dépôts stables. Ainsi, si vous avez Ubuntu ≤ 20.04 ou | ||||
|     Debian ≤ Buster, vous ne pourrez pas utiliser de lecteur vidéo | ||||
|     ni même diffuser avec votre machine. Vous devrez vous mettre à | ||||
|     jour vers Ubuntu 20.10 ou Debian Bullseye. | ||||
|   </p> | ||||
|  | ||||
|   <h2>Mentions légales</h2> | ||||
|   <p> | ||||
|     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. | ||||
|     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é. | ||||
|     Ce service est hébergé par l'association Crans, au | ||||
|     61 Avenue du Président Wilson, 94235 Cachan Cedex, France. | ||||
|     Ce service est hébergé par {{.Cfg.LegalMentionsEntity}}, au | ||||
|     {{.Cfg.LegalMentionsAddress}}. | ||||
|   </p> | ||||
|   <p> | ||||
|     <b>En cas de réclamation sur le contenu diffusé</b>, | ||||
|     la loi vous autorise à contacter directement l'hébergeur à | ||||
|     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 | ||||
|     courrier électronique à l'adresse <code>bureau[at]crans.org</code>. | ||||
|     courrier électronique à l'adresse <code>{{.Cfg.LegalMentionsEmail}}</code>. | ||||
|   </p> | ||||
| </div> | ||||
| {{end}} | ||||
|   | ||||
| @@ -6,14 +6,14 @@ | ||||
|  | ||||
|     <!-- Links and settings under video --> | ||||
|     <div class="controls"> | ||||
|       <span class="control-quality"> | ||||
|       <!-- <span class="control-quality"> | ||||
|         <select id="quality"> | ||||
|           <option value="source">Source</option> | ||||
|           <option value="720p">720p</option> | ||||
|           <option value="480p">480p</option> | ||||
|           <option value="240p">240p</option> | ||||
|         </select>   | ||||
|       </span> | ||||
|       </span> --> | ||||
|       <code class="control-srt-link">srt://{{.Cfg.Hostname}}:{{.Cfg.SRTServerPort}}?streamid={{.Path}}</code> | ||||
|       <span class="control-viewers" id="connected-people">0</span> | ||||
|       <svg class="control-indicator" id="connectionIndicator" fill="#dc3545" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> | ||||
| @@ -34,8 +34,18 @@ | ||||
|   {{end}} | ||||
| </div> | ||||
|  | ||||
| {{if .OMECfg.Enabled}} | ||||
|   <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/js/ovenplayer.js"></script> | ||||
| {{end}} | ||||
| <script type="module"> | ||||
|   import { initViewerPage } from "/static/js/viewer.js"; | ||||
|   {{if .OMECfg.Enabled}} | ||||
|     import { initViewerPage } from "/static/js/ovenplayer.js"; | ||||
|   {{else}} | ||||
|     import { initViewerPage } from "/static/js/viewer.js"; | ||||
|   {{end}} | ||||
|  | ||||
|   // Some variables that need to be fixed by web page | ||||
|   const viewersCounterRefreshPeriod = Number("{{.Cfg.ViewersCounterRefreshPeriod}}"); | ||||
| @@ -45,6 +55,10 @@ | ||||
|     "{{$value}}", | ||||
|     {{end}} | ||||
|   ] | ||||
|   initViewerPage(stream, stunServers, viewersCounterRefreshPeriod) | ||||
|   {{if .OMECfg.Enabled}} | ||||
|     initViewerPage(stream, {{.OMECfg.App}}, viewersCounterRefreshPeriod, {{.Cfg.PlayerPoster}}) | ||||
|   {{else}} | ||||
|     initViewerPage(stream, stunServers, viewersCounterRefreshPeriod) | ||||
|   {{end}} | ||||
| </script> | ||||
| {{end}} | ||||
|   | ||||
							
								
								
									
										10
									
								
								web/web.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								web/web.go
									
									
									
									
									
								
							| @@ -2,6 +2,7 @@ | ||||
| package web | ||||
|  | ||||
| import ( | ||||
| 	"gitlab.crans.org/nounous/ghostream/stream/ovenmediaengine" | ||||
| 	"html/template" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| @@ -27,11 +28,17 @@ type Options struct { | ||||
| 	STUNServers                 []string | ||||
| 	ViewersCounterRefreshPeriod int | ||||
| 	WidgetURL                   string | ||||
| 	LegalMentionsEntity         string | ||||
| 	LegalMentionsAddress        string | ||||
| 	LegalMentionsFullAddress    []string | ||||
| 	LegalMentionsEmail          string | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	cfg *Options | ||||
|  | ||||
| 	omeCfg *ovenmediaengine.Options | ||||
|  | ||||
| 	// Preload templates | ||||
| 	templates *template.Template | ||||
|  | ||||
| @@ -70,9 +77,10 @@ func loadTemplates() error { | ||||
| } | ||||
|  | ||||
| // Serve HTTP server | ||||
| func Serve(s *messaging.Streams, c *Options) { | ||||
| func Serve(s *messaging.Streams, c *Options, ome *ovenmediaengine.Options) { | ||||
| 	streams = s | ||||
| 	cfg = c | ||||
| 	omeCfg = ome | ||||
|  | ||||
| 	if !cfg.Enabled { | ||||
| 		// Web server is not enabled, ignore | ||||
|   | ||||
| @@ -36,7 +36,8 @@ func websocketHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 		err = conn.ReadJSON(c) | ||||
| 		if err != nil { | ||||
| 			log.Printf("Failed to receive client description: %s", err) | ||||
| 			continue | ||||
| 			_ = conn.Close() | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// Get requested stream | ||||
|   | ||||
		Reference in New Issue
	
	Block a user