!ffmpeg -i LlamaDrama.mp4 -movflags faststart -b:v 256000 -maxrate 256000 -x264opts "fps=24:keyint=48:min-keyint=48:no-scenecut" -hls_list_size 0 -hls_time 4 -hls_base_url http://192.168.3.14:8000/low/ low/LlamaDrama.m3u8

"""
Explanation: Introduction au Streaming adaptatif
Luc Trudeau
Au menu:
Implémentation HTTP Live Streaming
https://tools.ietf.org/html/draft-pantos-http-live-streaming-07
Logiciels requis
FFMPEG (Outil de codage)
https://ffmpeg.org/ffmpeg.html
GStreamer (Diffusion)
http://gstreamer.freedesktop.org/
Séquence Vidéo
http://www.caminandes.com/
Streaming Adaptatif
Idée de base:
End of explanation
"""


!ffmpeg -i LlamaDrama.mp4 -movflags faststart -b:v 512000 -maxrate 512000 -x264opts "fps=24:keyint=48:min-keyint=48:no-scenecut" -hls_list_size 0 -hls_time 4 -hls_base_url http://192.168.3.14:8000/medium/ medium/LlamaDrama.m3u8

"""
Explanation: Llama Drama Low (1920x1080)

256 kbits/secondes
End of explanation
"""


!ffmpeg -i LlamaDrama.mp4 -movflags faststart -b:v 1024000 -maxrate 1024000 -x264opts "fps=24:keyint=48:min-keyint=48:no-scenecut" -hls_list_size 0 -hls_time 4 -hls_base_url http://192.168.3.14:8000/high/ high/LlamaDrama.m3u8

"""
Explanation: Llama Drama Medium (1920x1080)

512 kbits/secondes
End of explanation
"""


from collections import namedtuple
from io import BytesIO
from requests import get
import m3u8
from time import time
from io import BytesIO
from subprocess import call

Stream = namedtuple('Stream',['bandwidth', 'uri'])

"""
Explanation: Llama Drama High (1920x1080)

1024 kbits/secondes
Le Manifeste
HLS utilise le format m3u8
/LlamaDrama.m3u8
```#EXTM3U
EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=256000
http://localhost:8000/low/LlamaDrama.m3u8
EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=512000
http://localhost:8000/medium/LlamaDrama.m3u8
EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1024000
http://localhost:8000/high/LlamaDrama.m3u8```
End of explanation
"""


class HLS:
    
    speed = 0 # Bits / second
    i = 0
    
    def __init__(self, uri):
        self.selector = StreamEngine(uri)
    
    def __iter__(self):
        return self
    
    def __next__(self):
        stream = self.selector.selectStream(self.speed)
        
        if self.i < len(stream.segments):
            startTime = time()
            buf = getSegment(stream.segments[self.i])
            self.speed = round((buf.getbuffer().nbytes*8) / (time() - startTime))
            print('%d bits/s' %self.speed)
            self.i += 1
            return buf
        else:
            raise StopIteration

"""
Explanation: Playlist
Chaque playlist pointe les segments de la séquence
/high/LlamaDrama.m3u8
```#EXTM3U
EXT-X-VERSION:3
EXT-X-TARGETDURATION:4
EXT-X-MEDIA-SEQUENCE:0
EXTINF:4.000000,
http://localhost:8000/high/LlamaDrama0.ts
EXTINF:4.000000,
http://localhost:8000/high/LlamaDrama1.ts
EXTINF:4.000000,
http://localhost:8000/high/LlamaDrama2.ts
EXTINF:4.000000,
http://localhost:8000/high/LlamaDrama3.ts
...
EXTINF:4.000000,
http://localhost:8000/high/LlamaDrama21.ts
EXTINF:1.875000,
http://localhost:8000/high/LlamaDrama22.ts
EXT-X-ENDLIST```
Client HLS
La classe HLS permet d'itérer a traver les segments.
Le temps est mesuré, lors du téléchargment du segment.
En combinant le temps et la taille du fichier, on obtient la vitesse.
Le StreamEngine choisit le stream approprié en fonction de la vitesse.

@startuml
skinparam style strictuml
skinparam dpi 300
class HLS << iterable >> {
    ByteIO next()
}
class StreamEngine {
    Stream selectStream(speed)
}
HLS -right-> StreamEngine
@enduml
Classe HLS
Le patron Iterator est utilisé pour itérer à travers l'ensemble des segments de la séquence.
End of explanation
"""


class StreamEngine:
    
    currentStream = None
    streamM3 = None
    streams = None
    
    def __init__(self, uri):
        self.streams = sorted([Stream(playlist.stream_info.bandwidth, playlist.uri) 
                               for playlist in m3u8.load(uri).playlists])
        self.currentStream = self.streams[0]
        self.streamM3 = m3u8.load(self.currentStream.uri)
    
    def selectStream(self, speed):
        newStream = self.currentStream
        
        for stream in self.streams:
            if stream.bandwidth < speed:
                newStream = stream
            else:
                break
        
        if newStream != self.currentStream:
            self.currentStream = newStream
            self.streamM3 = m3u8.load(newStream.uri)
            print('Changing Streams: New BitRate %d' %newStream.bandwidth)
        
        return self.streamM3

def getSegment(segment):
    buf = BytesIO()
    r = get(segment.uri, stream=True)
    for chunk in r.iter_content(chunk_size=2048): 
        if chunk:
            buf.write(chunk)
    return buf

"""
Explanation: StreamEngine
Le StreamEngine choisi le prochain segment en fonction de la vitesse de transfert
End of explanation
"""


from subprocess import Popen, PIPE, STDOUT

hls = HLS('http://192.168.3.14:8000/LlamaDrama.m3u8')
player = Popen("/usr/local/bin/gst-play-1.0 fd://0".split(), stdout=PIPE, stdin=PIPE)

for segment in hls:
    player.stdin.write(segment.getvalue())

"""
Explanation: Exemple réel
Le contenu des segments reçu par HLS est "pipé" dans GStreamer
Même, si GStreamer possède un buffer d'entrée, nous utilisons quand même un buffer interne pour ne pas blocker un téléchargement lorsque le buffer de GStreamer est plein.
L'appel de player.stdin.write étant bloquant, ceci va servir de mécanisme de controlle de flux pour les requêtes HLS.
End of explanation
"""


