23
Coder un streamer video en 135 lignes de Haskell et en 1 week-end Julien Dehos Lambda Lille 2020 (remote) 1 / 23

Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

  • Upload
    others

  • View
    0

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Coder un streamer video en 135 lignes de Haskellet en 1 week-end

Julien Dehos

Lambda Lille 2020 (remote)

1 / 23

Page 2: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Contexte

I je suis enseignant-chercheur à l’Université du Littoral

I Covid-19 ⇒ enseignement à distance

I Département Informatique : ouverture d’un serveur Discord(texte + audio + partage d’écran)

I ce que j’avais déjà : support de cours sur le web, dépôts Gitlab

2 / 23

Page 3: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Problème

I ce que j’avais pas : partage d’une zone de l’écran pour les TP(j’ai qu’un écran et je switche tout le temps entre Terminal,Vim, Firefox et Mypaint)

I connection ADSL ⇒ upload inférieur à 1 Mbits/s

I au moins 20 “spectateurs”

I testés mais rejetés : Discord, Zoom, Obs+Twitch, Gstreamer(écran complet ou 1 fenêtre, latence, débit. . . )

3 / 23

Page 4: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Solution envisagée

I un serveur HTTP qui fournit l’image courante à des clients web

I un programme sur ma machine qui envoie une capture d’écranau server via un websocket

I une image jpeg basse qualité à 2 fps suffit

4 / 23

Page 5: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Disclaimer

C’est du code moche, fait dans l’urgence !

I on est averti le vendredi

I j’ai TP le mardi suivant

I et il faut aussi faire une VM + une page web pour les consignes

5 / 23

Page 6: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Jalon 1 : serveur web

I une route / pour la page index.html

I une route /img pour l’image jpeg

I bibliothèque Haskell scotty

6 / 23

Page 7: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

I codevideo19-serve.hs :

main :: IO ()main = do

img <- BS.readFile "static/bob.jpg"imgRef <- newIORef imgport <- read . fromMaybe "3000" <$> lookupEnv "PORT"putStrLn $ "listening port " ++ show port ++ "..."SC.scotty port $ do

httpApp imgRef

httpApp :: IORef BS.ByteString -> SC.ScottyM ()httpApp imgRef = do

SC.get "/" $ SC.file "static/index.html"SC.get "/img" $ do

SC.addHeader "Content-Type" "image/jpeg"SC.raw =<< SC.liftAndCatchIO (readIORef imgRef)

7 / 23

Page 8: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

I static/index.html :

<html><head>

<meta charset="utf-8"/></head><body>

<img id="my_img"> </img><script>

function updateImg() {fetch("img")

.then(response => response.blob())

.then(function(myBlob){URL.revokeObjectURL(my_img.src);my_img.src = URL.createObjectURL(myBlob);

});}const my_interval = setInterval(updateImg, 500);

</script></body>

</html>

8 / 23

Page 9: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

9 / 23

Page 10: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Jalon 2 : websockets

I le client streamer envoie l’image via un websocket

I le server recupère l’image sur le websocket et la rend disponibleen HTTP

I bibliothèque Haskell websockets

10 / 23

Page 11: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

I covideo19-record.hs :

main :: IO ()main = do

img <- BS.readFile "static/gary.jpg"args <- getArgscase args of

[ip, portStr] -> doputStrLn $ "connecting " <> ip <> " on port " <> portStrlet app = clientApp img

port = read portStrWS.runClient ip port "" app

_ -> putStrLn "usage: <ip> <port>"

clientApp :: BS.ByteString -> WS.ClientApp ()clientApp img conn = forever $ do

WS.sendBinaryData conn imgthreadDelay 500000

11 / 23

Page 12: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

I covideo19-serve.hs :

main :: IO ()main = do

-- ...SC.scotty port $ do

SC.middleware (wsApp imgRef)httpApp imgRef

wsApp :: IORef BS.ByteString -> Application -> ApplicationwsApp imgRef =

websocketsOr WS.defaultConnectionOptions (wsHandle imgRef)

wsHandle :: IORef BS.ByteString -> WS.PendingConnection -> IO ()wsHandle imgRef pc = do

conn <- WS.acceptRequest pcforever (WS.receiveData conn >>= atomicWriteIORef imgRef)

httpApp :: IORef BS.ByteString -> SC.ScottyM ()-- ...

12 / 23

Page 13: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

13 / 23

Page 14: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Jalon 3 : capture d’écran

I l’image envoyée par le client streamer est une capture de l’écran

I bibliothèque Haskell haskell-gi (et ses dérivées)

14 / 23

Page 15: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

I covideo19-record.hs :

main :: IO ()main = do

window <- initGtk-- ...

initGtk :: IO Gdk.WindowinitGtk = do

_ <- Gtk.init NothingJust screen <- Gdk.screenGetDefaultGdk.screenGetRootWindow screen

clientApp :: Gdk.Window -> WS.ClientApp ()clientApp window conn = forever $ do

Just pxbuf <- Gdk.pixbufGetFromWindow window 0 0 800 600img <- pixbufSaveToBufferv pxbuf "jpeg" ["quality"] ["50"]WS.sendBinaryData conn imgobjectUnref pxbufthreadDelay 500000

15 / 23

Page 16: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

16 / 23

Page 17: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Image Docker

I release.nix :

letpkgs = import <nixpkgs> {};app-src = ./. ;app = pkgs.haskellPackages.callCabal2nix "covideo19" ./. {};

inpkgs.runCommand "covideo19" { inherit app; } ''

mkdir -p $out/{bin,static}cp ${app}/bin/covideo19-serve $out/bin/cp ${app-src}/static/* $out/static/

''

17 / 23

Page 18: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

I docker.nix :

{ pkgs ? import <nixpkgs> {} }:let

app = import ./release.nix;entrypoint = pkgs.writeScript "entrypoint.sh" ''

#!${pkgs.stdenv.shell}$@

'';in

pkgs.dockerTools.buildLayeredImage {name = "covideo19";tag = "test";config = {

WorkingDir = "${app}";Entrypoint = [ entrypoint ];Cmd = [ "${app}/bin/covideo19-serve" ];

};}

18 / 23

Page 19: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

DéploiementI construire l’image Docker :

nix-build docker.nixdocker load < result

I déployer l’image sur Heroku :

heroku loginheroku container:loginheroku create covideo19testdocker tag covideo19:test registry.heroku.com/covideo19test/webdocker push registry.heroku.com/covideo19test/webheroku container:release web --app covideo19test

I lancer le stream :

covideo19-record covideo19.herokuapp.com 80

19 / 23

Page 20: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Résultat

I ici, 60 lignes de Haskell

I mais dans la vraie appli :I affichage du curseurI paramètres supplémentaires (région à streamer, compression)I clé d’autorisation pour streamerI page de monitoringI gestion d’erreur (un peu)

20 / 23

Page 21: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Retour d’expérience

I conclusion 1 :I l’appli est moche mais bien pratique pour mon cas d’utilisationI testée en 800x600, 2 fps, qualité 25% (jusqu’à 25 clients web)

I conclusion 2 :I Haskell c’est cool : typage, fonctionnel pur, libs. . .I je connaissais déjà scotty/websockets/docker/heroku ⇒ RASI je connaissais presque pas haskell-gi ⇒ j’ai un peu galéré pour

trouver les bonnes docs et exemples

21 / 23

Page 22: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Références

I projet : https://gitlab.com/juliendehos/covideo19

I slides : https://gitlab.com/juliendehos/talk-2020-lambdalille-covideo19

22 / 23

Page 23: Coder un streamer video en 135 lignes de Haskell et en 1 ... · Coder un streamer video en 135 lignes de Haskell et en 1 week-end JulienDehos LambdaLille2020(remote) 1/23

Merci ! Questions ou commentaires ?

23 / 23