diff --git a/playout/playsync.py b/playout/playsync.py new file mode 100755 index 0000000..12448ed --- /dev/null +++ b/playout/playsync.py @@ -0,0 +1,159 @@ +#!/usr/bin/python3 +import argparse +import datetime +import os +import random +import socket +import string +import sys +import time +from glob import glob +from queue import Queue +from socketserver import UDPServer, ThreadingMixIn, BaseRequestHandler +from threading import Thread + +import ox +import mpv + +import logging +logger = logging.getLogger('cdosea') + +DEFAULT_PORT=2680 +DEBUG=False + +def update_playlist(playlist, prefix='video/'): + playlist = 'play.m3u' + + today = datetime.date.today() + seconds_since_midnight = time.time() - time.mktime(today.timetuple()) + + files = [] + videos = {} + for letter in string.ascii_uppercase: + videos[letter] = glob('%s%s*.mp4' % (prefix, letter.lower())) + random.shuffle(videos[letter]) + + for i in range(10): + for letter in string.ascii_uppercase: + files.append(videos[letter][i]) + + position = 0 + while position < seconds_since_midnight: + f = files.pop(0) + try: + position += ox.avinfo(f)['duration'] + files.append(f) + except: + pass + + with open(playlist, 'w') as f: + f.write('\n'.join(files)) + +class ThreadingUDPServer(ThreadingMixIn, UDPServer): + pass + +class Handler(BaseRequestHandler): + + def handle(self): + data = self.request[0].strip() + socket = self.request[1] + logger.debug("%s wrote: %s", self.client_address[0], data) + if self.server.player.playing: + response = b'PLAYING' + else: + response = b'WAITING' + socket.sendto(response, self.client_address) + self.server.player.queue.put('') + + +class Player(Thread): + first = True + running = True + playing = False + current = '' + + def __init__(self, peer, player, playlist): + self.peer = peer + self.playlist = playlist + self.player = player + self.queue = Queue() + Thread.__init__(self) + self.daemon = True + self.start() + + def run(self): + logger.debug("update playlist") + update_playlist(self.playlist) + logger.debug("load playlist") + self.player.loadlist(self.playlist) + self.player.pause = True + while self.running: + logger.debug("play") + self.play() + logger.debug("ping") + self.ping() + logger.debug("wait") + self.queue.get() + + def play(self): + self.playing = True + self.player.pause = False + self.player.wait_for_playback() + self.player.pause = True + self.playing = False + + def ping(self): + data = 'NEXT' + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + logger.debug("ping: %s %s", self.peer, data) + sock.sendto(bytes(data + "\n", "utf-8"), self.peer) + received = str(sock.recv(1024), "utf-8") + if received == 'WAITING' and not self.queue.qsize(): + if self.first: + self.queue.put('') + self.first = False + logger.debug("Sent: %s", data) + logger.debug("Received: %s", received) + + +def my_log(loglevel, component, message): + logger.debug('[{}] {}: {}'.format(loglevel, component, message)) + +def main(): + parser = argparse.ArgumentParser(description='play 2 screens in sync') + parser.add_argument('--peer', help='ip[:port] of peer', required=True) + parser.add_argument('--port', type=int, help='local port', default=DEFAULT_PORT) + parser.add_argument('--playlist', help='play.m3u', default='play.m3u') + parser.add_argument('--window', action='store_true', help='run in window', default=False) + parser.add_argument('--debug', action='store_true', help='debug', default=False) + args = parser.parse_args() + + if ':' in args.peer: + peer = args.peer.split(':') + peer = (peer[0], int(peer[1])) + else: + peer = (args.peer, DEFAULT_PORT) + + DEBUG = args.debug + if DEBUG: + log_format = '%(asctime)s:%(levelname)s:%(name)s:%(message)s' + logging.basicConfig(level=logging.DEBUG, format=log_format) + base = os.path.dirname(os.path.abspath(__file__)) + os.chdir(base) + + player = mpv.MPV(log_handler=my_log, input_default_bindings=True, input_vo_keyboard=True) + player.fullscreen = not args.window + player.loop = 'inf' + server = ThreadingUDPServer(('0.0.0.0', args.port), Handler) + server.player = Player(peer, player, args.playlist) + + logger.debug("listen on %s...", args.port) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + del player + + +if __name__ == "__main__": + main()