diff --git a/group_vars/nginx_rtmp.yml b/group_vars/nginx_rtmp.yml new file mode 100644 index 0000000000000000000000000000000000000000..d5626daa0dca8487e3fa234c57ca8db1076db5f8 --- /dev/null +++ b/group_vars/nginx_rtmp.yml @@ -0,0 +1,4 @@ +--- + +glob_nginx_rtmp: + uri: stream.crans.org diff --git a/group_vars/reverseproxy.yml b/group_vars/reverseproxy.yml index cd01d6a323b9537d0bfe0c84003b2a088d386bdd..fa185203f22c240f5ba2f4d8a7236364c3cce3fc 100644 --- a/group_vars/reverseproxy.yml +++ b/group_vars/reverseproxy.yml @@ -48,6 +48,7 @@ nginx: # - {from: grafana.crans.org, to: "10.231.136.102:3000"} # - {from: webirc.crans.org, to: "10.231.136.1:9000"} - {from: framadate.crans.org, to: 172.16.10.109} + - {from: stream.crans.org, to: 172.16.10.118} # - {from: mailman.crans.org, to: 10.231.136.180} # # # Zamok diff --git a/hosts b/hosts index 22b567d8b4822a61c56d0bbdd78858ee602b4c11..b6c94842630b9bb96ef623050b7c4b79d4dbb828 100644 --- a/hosts +++ b/hosts @@ -25,6 +25,9 @@ # [test_vm] # re2o-test.adm.crans.org +[nginx_rtmp] +fluxx.adm.crans.org + [reverseproxy] hodaur.adm.crans.org frontdaur.adm.crans.org @@ -81,6 +84,7 @@ gitlab-ci.adm.crans.org hodaur.adm.crans.org monitoring.adm.crans.org boeing.adm.crans.org +fluxx.adm.crans.org [ovh_physical] sputnik.adm.crans.org diff --git a/plays/nginx_rtmp.yml b/plays/nginx_rtmp.yml new file mode 100755 index 0000000000000000000000000000000000000000..b515bb231fb5122a3b8779d5ddfaa2923be40087 --- /dev/null +++ b/plays/nginx_rtmp.yml @@ -0,0 +1,7 @@ +#!/usr/bin/env ansible-playbook +--- +- hosts: nginx_rtmp + vars: + nginx_rtmp: "{{ glob_nginx_rtmp | default({}) | combine(loc_nginx_rtmp | default({})) }}" + roles: + - nginx-rtmp diff --git a/roles/nginx-rtmp/files/index.html b/roles/nginx-rtmp/files/index.html new file mode 100644 index 0000000000000000000000000000000000000000..3f0323cc83bc1998c5d66bcc2e03588f9e59dfc3 --- /dev/null +++ b/roles/nginx-rtmp/files/index.html @@ -0,0 +1,129 @@ +<!DOCTYPE html> +<html lang="fr"> +<head> + <meta charset="UTF-8"> + <title>Crans Stream</title> + <link href="lib/bootstrap4/css/bootstrap.min.css" rel="stylesheet"> + <link href="//unpkg.com/video.js@7/dist/video-js.min.css" rel="stylesheet"> + <style> + body, html { + height: 100%; + } + .video-js .vjs-big-play-button { + display: none; + } + </style> +</head> +<body class="bg-dark"> + <div class="container h-100 p-3"> + <div class="row align-items-center h-100"> + <div class="col-12"> + <video id="my-video" class="video-js embed-responsive shadow-lg rounded-sm"></video> + <p class="text-right text-white"> + <a class="text-white text-decoration-none" href="#" id="linkStream">Ouvrir le flux dans une application externe</a> + — + <a class="text-white text-decoration-none" href="#aboutModal" data-toggle="modal">À propos</a> + </p> + </div> + </div> + </div> + + <div class="modal fade" id="aboutModal" tabindex="-1"> + <div class="modal-dialog-centered modal-dialog modal-lg"> + <div class="modal-content"> + <div class="modal-header"> + <h4 class="modal-title" id="exampleModalLabel">À propos</h4> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <p>Crans Stream est un service maintenu par le <a href="https://crans.org/">Crans</a> permettant de diffuser un contenu vidéo. Il a pour but d'être utilisé pour diffuser des séminaires ou évènements.</p> + + <h4>Comment je diffuse ?</h4> + <p>Pour diffuser un contenu vous devez être adhérent Crans et être sur le campus de Cachan.</p> + <h5>Avec Open Broadcaster Software</h5> + <p> + <a href="https://obsproject.com/">Open Broadcaster Software (OBS)</a> + est une solution libre et open source de diffusion vidéo. + Pour diffuser sur cette plateforme, allez dans l'onglet « Stream (flux) » des paramètres : + </p> + <ul> + <li><b>Serveur :</b> <code>rtmp://stream.adm.crans.org/live</code>,</li> + <li><b>Clé de stream :</b> votre identifiant.</li> + </ul> + <h5>Avec FFmpeg</h5> + <p> + <code> + ffmpeg -re -i mavideo.webm -vcodec libx264 -vprofile baseline + -acodec aac -strict -2 -f flv + rtmp://stream.adm.crans.org/live/identifiant + </code> + </p> + + <h4>Mentions légales</h4> + <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 + 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. + </p> + <p> + <b>En cas de réclamation sur le contenu diffusé</b> de type + <code>https://stream.crans.org/identifiant</code>, + l'auteur peut être contacté par courrier à l'adresse + <code>identifiant@crans.org</code>. + La loi vous autorise à contacter directement l'hébergeur à + l'adresse suivante : + <pre>Association Crans - ENS Paris-Saclay<br/>Notification de Contenus Illicites<br/>61, Avenue du Président Wilson<br/>94235 Cachan Cedex<br/>France</pre> + Vous pouvez également envoyer directement vos réclamations par + courrier électronique à l'adresse <code>bureau[at]crans.org</code>. + </p> + </div> + </div> + </div> + </div> + + <script src="lib/jquery/jquery.min.js"></script> + <script src="lib/bootstrap4/js/bootstrap.min.js"></script> + <script src="//unpkg.com/video.js@7/dist/video.min.js"></script> + <script> + // Get current stream + let streamId = window.location.pathname.split("/").pop() + if (streamId.length === 0) { + streamId = "crans" // default on crans account + } + const streamUrl = `/hls/${streamId}.m3u8` + document.getElementById("linkStream").href = streamUrl + + // If stream exists, then load + const player = videojs('my-video', { + controls: true, + autoplay: true, + preload: 'auto', + muted: true, + fluid: true, + aspectRatio: '16:9' + }) + + fetch(streamUrl, { + method: 'HEAD', + cache: 'no-cache' + }).then((response) => { + if (response.ok) { + // Stream exists, load! + player.src({ + type: 'application/x-mpegURL', + src: streamUrl + }); + } else { + // Stream does not exist, alert! + player.poster('/no-stream.jpg') + } + }); + </script> +</body> +</html> diff --git a/roles/nginx-rtmp/files/no-stream.jpg b/roles/nginx-rtmp/files/no-stream.jpg new file mode 100644 index 0000000000000000000000000000000000000000..af44c5a43ea0399422d9fe21c302a262c53f7a3e Binary files /dev/null and b/roles/nginx-rtmp/files/no-stream.jpg differ diff --git a/roles/nginx-rtmp/handlers/main.yml b/roles/nginx-rtmp/handlers/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..3b8f2d6927c32e1ccdb51d04f84b0944f2aa7d93 --- /dev/null +++ b/roles/nginx-rtmp/handlers/main.yml @@ -0,0 +1,7 @@ +--- + +- name: restart nginx + service: + name: nginx + state: restarted + become: true diff --git a/roles/nginx-rtmp/tasks/main.yml b/roles/nginx-rtmp/tasks/main.yml new file mode 100644 index 0000000000000000000000000000000000000000..c1e61bfdc96a081d21db285bda84327a94b18d65 --- /dev/null +++ b/roles/nginx-rtmp/tasks/main.yml @@ -0,0 +1,54 @@ +--- + +- name: Install nginx with rtmp module + apt: + name: + - nginx-full + - libnginx-mod-rtmp + - libjs-bootstrap4 + register: apt_result + retries: 3 + until: apt_result is succeeded + become: yes + +- name: Copy module and site configuration files + template: + src: "{{ item }}.j2" + dest: "/etc/{{ item }}" + loop: + - nginx/modules-available/60-rtmp.conf + - nginx/sites-available/stream + notify: restart nginx + +- name: Enable NGINX site + file: + src: /etc/nginx/sites-available/stream + dest: /etc/nginx/sites-enabled/stream + state: link + notify: restart nginx + +- name: Enable RTMP module + file: + src: /etc/nginx/modules-available/60-rtmp.conf + dest: /etc/nginx/modules-enabled/60-rtmp.conf + state: link + notify: restart nginx + +- name: Create site folder + file: + path: /var/www/stream/hls + state: directory + +- name: Copy index.html and assets + copy: + src: "{{ item }}" + dest: "/var/www/stream/{{ item }}" + loop: + - index.html + - no-stream.jpg + +- name: Link javascript libs + file: + src: /usr/share/javascript + dest: /var/www/stream/lib + state: link diff --git a/roles/nginx-rtmp/templates/nginx/modules-available/60-rtmp.conf.j2 b/roles/nginx-rtmp/templates/nginx/modules-available/60-rtmp.conf.j2 new file mode 100644 index 0000000000000000000000000000000000000000..e780fb6c9a9842a58339015195642b734fad73d4 --- /dev/null +++ b/roles/nginx-rtmp/templates/nginx/modules-available/60-rtmp.conf.j2 @@ -0,0 +1,18 @@ +{{ ansible_header | comment }} + +rtmp { + server { + listen 1935; + chunk_size 4096; + application live { + live on; + hls on; + hls_path /var/www/stream/hls/; + hls_fragment 3; + hls_playlist_length 20; + + record off; + } + } +} + diff --git a/roles/nginx-rtmp/templates/nginx/sites-available/stream.j2 b/roles/nginx-rtmp/templates/nginx/sites-available/stream.j2 new file mode 100644 index 0000000000000000000000000000000000000000..2a074c8da1ec704eaf1a416aaccf9cc1f0f3ed19 --- /dev/null +++ b/roles/nginx-rtmp/templates/nginx/sites-available/stream.j2 @@ -0,0 +1,40 @@ +{{ ansible_header | comment }} + +server { + listen 80; + listen [::]:80; + + server_name {{ nginx_rtmp.uri }}; + + root /var/www/stream; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /hls { + # Disable cache + add_header Cache-Control no-cache; + + # CORS setup + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Expose-Headers' 'Content-Length'; + + # allow CORS preflight requests + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + add_header 'Content-Length' 0; + return 204; + } + + types { + application/dash+xml mpd; + application/vnd.apple.mpegurl m3u8; + video/mp2t ts; + } + } +} +