63 lines
2.5 KiB
Python
63 lines
2.5 KiB
Python
import os
|
|
from datetime import datetime, timedelta
|
|
|
|
import pause
|
|
import pydub
|
|
from pydub.utils import make_chunks
|
|
|
|
from source.behaviors.roles import base
|
|
from source.managers import Manager
|
|
from source.packets import AudioPacket
|
|
from source.utils.crypto.type import CipherType
|
|
|
|
|
|
class MasterRole(base.BaseRole):
|
|
"""
|
|
Role used when the machine is declared as the master.
|
|
It will be the machine responsible for emitting data that the others peers should play together.
|
|
"""
|
|
|
|
TARGET_SIZE: int = 60 * 1024 # set an upper bound because of the IPv6 maximum packet size.
|
|
|
|
def __init__(self, manager: "Manager"):
|
|
super().__init__(manager)
|
|
|
|
# generate a random secret key for AES communication
|
|
self.manager.communication.secret_key = os.urandom(32)
|
|
|
|
# prepare the audio file that will be streamed
|
|
# TODO(Faraphel): use another audio source
|
|
self.audio = pydub.AudioSegment.from_file("../assets/Caravan Palace - Wonderland.mp3")
|
|
self.play_time = datetime.now()
|
|
|
|
# calculate the number of bytes per milliseconds in the audio
|
|
bytes_per_ms = self.audio.frame_rate * self.audio.sample_width * self.audio.channels / 1000
|
|
# calculate the required chunk duration to reach that size
|
|
self.chunk_duration = timedelta(milliseconds=self.TARGET_SIZE / bytes_per_ms)
|
|
|
|
# split the audio into chunks
|
|
self.chunks = make_chunks(self.audio, self.chunk_duration.total_seconds() * 1000)
|
|
|
|
|
|
def handle(self) -> None:
|
|
# TODO(Faraphel): communicate with chrony to add peers and enable server mode ?
|
|
# TODO(Faraphel): share the secret key generated with the other *allowed* peers ! How to select them ? A file ?
|
|
# TODO(Faraphel): check if another server is emitting sound in the network. Return to undefined if yes
|
|
|
|
# get the next chunk
|
|
chunk = self.chunks.pop(0)
|
|
|
|
# broadcast it in the network
|
|
audio_packet = AudioPacket(
|
|
# TODO(Faraphel): adjust time depending on the network average latency ?
|
|
datetime.now() + timedelta(seconds=5), # play it in some seconds to let all the machines get the sample
|
|
chunk.channels,
|
|
chunk.frame_rate,
|
|
chunk.sample_width,
|
|
chunk.raw_data,
|
|
)
|
|
self.manager.communication.broadcast(audio_packet, CipherType.AES_CBC)
|
|
|
|
# wait for the audio to play
|
|
# TODO(Faraphel): should adapt to the compute time above
|
|
pause.until(datetime.now() + self.chunk_duration)
|